From 8bfc8971a47ecde1e0bf2d06e75728c446286fd2 Mon Sep 17 00:00:00 2001 From: Ritesh Madapurath Date: Tue, 29 Mar 2016 02:18:27 -0700 Subject: [PATCH] Adding Non-ampp support for VDX Add support for NON-AMPP(VLAN) based networking on VDX switches. NON-AMPP based networking can be accessed through a seperate mechanism driver. Change-Id: I4e67ff816af71ebbed1be29023c8b3c652ab3f73 --- networking_brocade/vdx/ampp/__init__.py | 0 .../vdx/ampp/ml2driver/__init__.py | 0 .../vdx/ampp/ml2driver/mechanism_brocade.py | 464 ++++++++ .../vdx/ampp/ml2driver/nos/__init__.py | 0 .../vdx/ampp/ml2driver/nos/nctemplates.py | 433 +++++++ .../vdx/ampp/ml2driver/nos/nosdriver.py | 488 ++++++++ networking_brocade/vdx/ampp/tests/__init__.py | 0 .../vdx/ampp/tests/unit/__init__.py | 0 .../vdx/ampp/tests/unit/ml2/__init__.py | 0 .../ampp/tests/unit/ml2/drivers/__init__.py | 0 .../unit/ml2/drivers/brocade/__init__.py | 0 .../drivers/brocade/test_brocade_l3_plugin.py | 57 + .../brocade/test_brocade_mechanism_driver.py | 118 ++ networking_brocade/vdx/bare_metal/__init__.py | 0 .../vdx/bare_metal/mechanism_brocade.py | 200 ++++ networking_brocade/vdx/bare_metal/util.py | 235 ++++ networking_brocade/vdx/db/__init__.py | 0 .../vdx/db/migration/__init__.py | 0 .../migration/alembic_migrations/__init__.py | 0 .../db/migration/alembic_migrations/env.py | 123 ++ .../alembic_migrations/versions/HEAD.bk | 1 + .../alembic_migrations/versions/HEADS | 2 + .../versions/kilo_release.py | 27 + .../4fadb44a1e2d_ml2_brcd_contract.py | 35 + .../versions/liberty/contract/__init__.py | 0 .../versions/liberty/expand/__init__.py | 0 .../liberty/expand/a84d6a05d397_ml2_brcd.py | 53 + .../start_networking_brocade_migration.py | 34 + networking_brocade/vdx/db/models.py | 220 ++++ .../vdx/ml2driver/mechanism_brocade.py | 5 +- .../vdx/ml2driver/nos/nosdriver.py | 3 +- networking_brocade/vdx/non_ampp/__init__.py | 0 .../vdx/non_ampp/ml2driver/__init__.py | 0 .../ml2driver/brocade_fwaas_driver.py | 293 +++++ .../ml2driver/brocade_fwaas_plugin.py | 230 ++++ .../vdx/non_ampp/ml2driver/fwaas_plugin.py | 408 +++++++ .../non_ampp/ml2driver/l3_router_plugin.py | 346 ++++++ .../non_ampp/ml2driver/mechanism_brocade.py | 493 ++++++++ .../vdx/non_ampp/ml2driver/nos/__init__.py | 0 .../vdx/non_ampp/ml2driver/nos/nctemplates.py | 825 ++++++++++++++ .../vdx/non_ampp/ml2driver/nos/nosdriver.py | 1013 +++++++++++++++++ .../vdx/non_ampp/ml2driver/utils.py | 615 ++++++++++ .../services/l3_router/l3_router_plugin.py | 8 +- .../drivers/brocade/test_brocade_l3_plugin.py | 2 +- setup.cfg | 6 + tox.ini | 2 +- 46 files changed, 6730 insertions(+), 9 deletions(-) create mode 100644 networking_brocade/vdx/ampp/__init__.py create mode 100644 networking_brocade/vdx/ampp/ml2driver/__init__.py create mode 100644 networking_brocade/vdx/ampp/ml2driver/mechanism_brocade.py create mode 100644 networking_brocade/vdx/ampp/ml2driver/nos/__init__.py create mode 100644 networking_brocade/vdx/ampp/ml2driver/nos/nctemplates.py create mode 100644 networking_brocade/vdx/ampp/ml2driver/nos/nosdriver.py create mode 100644 networking_brocade/vdx/ampp/tests/__init__.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/__init__.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/ml2/__init__.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/ml2/drivers/__init__.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/__init__.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py create mode 100644 networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py create mode 100644 networking_brocade/vdx/bare_metal/__init__.py create mode 100644 networking_brocade/vdx/bare_metal/mechanism_brocade.py create mode 100644 networking_brocade/vdx/bare_metal/util.py create mode 100644 networking_brocade/vdx/db/__init__.py create mode 100644 networking_brocade/vdx/db/migration/__init__.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/__init__.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/env.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/HEAD.bk create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/HEADS create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/kilo_release.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/4fadb44a1e2d_ml2_brcd_contract.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/__init__.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/__init__.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/a84d6a05d397_ml2_brcd.py create mode 100644 networking_brocade/vdx/db/migration/alembic_migrations/versions/start_networking_brocade_migration.py create mode 100644 networking_brocade/vdx/db/models.py create mode 100644 networking_brocade/vdx/non_ampp/__init__.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/__init__.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_driver.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_plugin.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/fwaas_plugin.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/l3_router_plugin.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/mechanism_brocade.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/nos/__init__.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/nos/nctemplates.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/nos/nosdriver.py create mode 100644 networking_brocade/vdx/non_ampp/ml2driver/utils.py diff --git a/networking_brocade/vdx/ampp/__init__.py b/networking_brocade/vdx/ampp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/ml2driver/__init__.py b/networking_brocade/vdx/ampp/ml2driver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/ml2driver/mechanism_brocade.py b/networking_brocade/vdx/ampp/ml2driver/mechanism_brocade.py new file mode 100644 index 0000000..ecdbd3c --- /dev/null +++ b/networking_brocade/vdx/ampp/ml2driver/mechanism_brocade.py @@ -0,0 +1,464 @@ +# Copyright 2016 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. + + +"""Implentation of Brocade ML2 Mechanism driver for ML2 Plugin.""" + +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.db import models as brocade_db +from neutron.plugins.common import constants as p_const +from neutron.plugins.ml2 import driver_api +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import importutils + +LOG = logging.getLogger(__name__) +MECHANISM_VERSION = 0.9 +NOS_DRIVER = 'networking_brocade.vdx.ampp.ml2driver.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=_('OS Type of the switch')), + cfg.StrOpt('osversion', default='4.0.0', + help=_('OS Version number')) + ] + +cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade") + + +class BrocadeMechanism(driver_api.MechanismDriver): + + """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() + self._driver.close_session() + + def brocade_init(self): + """Brocade specific initialization for this class.""" + + osversion = None + self._switch = { + 'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'ostype': cfg.CONF.ml2_brocade.ostype, + 'osversion': cfg.CONF.ml2_brocade.osversion} + + self._driver = importutils.import_object(NOS_DRIVER) + + # Detect version of NOS on the switch + osversion = self._switch['osversion'] + if osversion == "autodetect": + osversion = self._driver.get_nos_version( + self._switch['address'], + self._switch['username'], + self._switch['password']) + + virtual_fabric_enabled = self._driver.is_virtual_fabric_enabled( + self._switch['address'], + self._switch['username'], + self._switch['password']) + + if virtual_fabric_enabled: + LOG.debug("Virtual Fabric: enabled") + else: + LOG.debug("Virtual Fabric: not enabled") + + self.set_features_enabled(osversion, virtual_fabric_enabled) + + def is_flat_network(self, segment): + if not segment or segment['network_type'] == p_const.TYPE_FLAT: + LOG.info(_LI("Flat network nothing to be done")) + return True + return False + + def set_features_enabled(self, nos_version, virtual_fabric_enabled): + self._virtual_fabric_enabled = virtual_fabric_enabled + version = nos_version.split(".", 2) + + # Starting 4.1.0 port profile domains are supported + if int(version[0]) >= 5 or (int(version[0]) >= 4 and + int(version[1]) >= 1): + self._pp_domains_supported = True + else: + self._pp_domains_supported = False + self._driver.set_features_enabled(self._pp_domains_supported, + self._virtual_fabric_enabled) + + def get_features_enabled(self): + return self._pp_domains_supported, self._virtual_fabric_enabled + + def create_network_precommit(self, mech_context): + """Create Network in the mechanism specific database table.""" + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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 not in [p_const.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( + _LE("Brocade Mechanism: failed to create network in db")) + raise Exception( + _("Brocade Mechanism: create_network_precommit failed")) + + LOG.info(_LI("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") + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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(_LE("Brocade NOS driver: failed in create network")) + brocade_db.delete_network(context, network_id) + raise Exception( + _("Brocade Mechanism: create_network_postcommmit failed")) + + LOG.info(_LI("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") + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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( + _LE("Brocade Mechanism: failed to delete network in db")) + raise Exception( + _("Brocade Mechanism: delete_network_precommit failed")) + + LOG.info(_LI("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. + + This translates to removng portprofile + from the switch. + """ + + LOG.debug("delete_network_postcommit: called") + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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(_LE("Brocade NOS driver: failed to delete network")) + raise Exception( + _("Brocade switch exception, " + "delete_network_postcommit failed")) + + LOG.info(_LI("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") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + 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, + None) + except Exception: + LOG.exception(_LE("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") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + 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) + if not network: + LOG.info(_LI("network not populated nothing to be done")) + return + 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( + _LE("Brocade NOS driver: failed to associate mac %s"), + interface_mac) + raise Exception( + _("Brocade switch exception: create_port_postcommit failed")) + + LOG.info( + _LI("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") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + 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(_LE("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") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + 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) + if not network: + LOG.info(_LI("network not populated nothing to be done")) + return + 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( + _LE("Brocade NOS driver: failed to dissociate MAC %s"), + interface_mac) + raise Exception( + _("Brocade switch exception, delete_port_postcommit failed")) + + LOG.info( + _LI("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/networking_brocade/vdx/ampp/ml2driver/nos/__init__.py b/networking_brocade/vdx/ampp/ml2driver/nos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/ml2driver/nos/nctemplates.py b/networking_brocade/vdx/ampp/ml2driver/nos/nctemplates.py new file mode 100644 index 0000000..f52c8c9 --- /dev/null +++ b/networking_brocade/vdx/ampp/ml2driver/nos/nctemplates.py @@ -0,0 +1,433 @@ +# 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. + + +"""NOS NETCONF XML Configuration Command Templates. + +Interface Configuration Commands +""" + +# Get NOS Version +SHOW_FIRMWARE_VERSION = ( + "show-firmware-version xmlns:nc=" + "'urn:brocade.com:mgmt:brocade-firmware-ext'" +) +GET_VCS_DETAILS = ( + 'get-vcs-details xmlns:nc="urn:brocade.com:mgmt:brocade-vcs"' +) +SHOW_VIRTUAL_FABRIC = ( + 'show-virtual-fabric xmlns:nc="urn:brocade.com:mgmt:brocade-vcs"' +) +GET_VIRTUAL_FABRIC_INFO = ( + 'interface xmlns:nc="urn:brocade.com:mgmt:brocade-firmware-ext"' +) + +NOS_VERSION = "./*/{urn:brocade.com:mgmt:brocade-firmware-ext}os-version" +VFAB_ENABLE = "./*/*/*/{urn:brocade.com:mgmt:brocade-vcs}vfab-enable" + +# 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_IN_DOMAIN = """ + + + {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} + + + + +""" + +# port-profile domain management commands +REMOVE_PORTPROFILE_FROM_DOMAIN = """ + + + {domain_name} + + {name} + + + +""" +# put port profile in default domain +CONFIGURE_PORTPROFILE_IN_DOMAIN = """ + + + {domain_name} + + {name} + + + +""" + +# +# L3 Life-cycle Management Configuration Commands +# + +# Create SVI and assign ippaddres (rbridge_id,vlan_id,ip_address) +CONFIGURE_SVI_WITH_IP_ADDRESS = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{ip_address}
+
+
+
+
+
+
+
+""" + +# delete SVI (rbridge_id,vlan_id) +DELETE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + +""" + +# Activate SVI (rbridge_id,vlan_id) +ACTIVATE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + + +""" + +# Remove ipaddress from SVI (rbridge_id,vlan_id) +DECONFIGURE_IP_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{gw_ip}
+
+
+
+
+
+
+
+""" + +# create vrf (rbridge_id,vrf_name) +CREATE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + + +# delete vrf (rbridge_id,vrf_name) +DELETE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + +# configure route distinguisher for vrf (rbridge_id,vrf_name, rd) +CONFIGURE_RD_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + {rd} + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF_V1 = """ + + + {rbridge_id} + + {vrf_name} + + + 1200 + + + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + + + + + + +""" + +# Bind vrf to SVI (rbridge_id,vlan_idi, vrf) +ADD_VRF_TO_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + +# unbind vrf from SVI (rbridge_id,vlan_idi, vrf) +DELETE_VRF_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + +# +# Constants +# + +# Port profile naming convention for Neutron networks +OS_PORT_PROFILE_NAME = "openstack-profile-{id}" +OS_VRF_NAME = "osv-{id}" + +# Port profile filter expressions +PORT_PROFILE_XPATH_FILTER = "/port-profile" +PORT_PROFILE_NAME_XPATH_FILTER = "/port-profile[name='{name}']" diff --git a/networking_brocade/vdx/ampp/ml2driver/nos/nosdriver.py b/networking_brocade/vdx/ampp/ml2driver/nos/nosdriver.py new file mode 100644 index 0000000..a09553b --- /dev/null +++ b/networking_brocade/vdx/ampp/ml2driver/nos/nosdriver.py @@ -0,0 +1,488 @@ +# Copyright 2016 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. + + +"""Brocade NOS Driver implements NETCONF over SSHv2 for +Neutron network life-cycle management. +""" + +from ncclient import manager +from oslo_log import log as logging +from oslo_utils import excutils + +from xml.etree import ElementTree + +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade.vdx.ampp.ml2driver.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(object): + + """NOS NETCONF interface driver for Neutron network. + + Handles life-cycle management of Neutron network (leverages AMPP on NOS) + """ + + def __init__(self): + self.mgr = None + self._virtual_fabric_enabled = False + self._pp_domains_supported = False + + def set_features_enabled(self, pp_domains_supported, + virtual_fabric_enabled): + """Set features in the driver based on what was detected by the MD.""" + self._pp_domains_supported = pp_domains_supported + self._virtual_fabric_enabled = virtual_fabric_enabled + + def get_features_enabled(self): + """Respond to status of features enabled.""" + return self._pp_domains_supported, self._virtual_fabric_enabled + + 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: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Connect failed to switch")) + + 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 get_nos_version(self, host, username, password): + """Show version of NOS.""" + try: + mgr = self.connect(host, username, password) + return self.nos_version_request(mgr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def is_virtual_fabric_enabled(self, host, username, password): + """Show version of NOS.""" + try: + mgr = self.connect(host, username, password) + return (self.virtual_fabric_info(mgr) == "enabled") + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def create_network(self, host, username, password, net_id): + """Creates a new virtual network.""" + + domain_name = "default" + 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) + + if self._pp_domains_supported and self._virtual_fabric_enabled: + self.configure_port_profile_in_domain(mgr, domain_name, name) + + self.create_vlan_profile_for_port_profile(mgr, name) + + if self._pp_domains_supported: + self.configure_l2_mode_for_vlan_profile_with_domains(mgr, name) + else: + 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(_LE("NETCONF error")) + self.close_session() + + def delete_network(self, host, username, password, net_id): + """Deletes a virtual network.""" + + domain_name = "default" + name = template.OS_PORT_PROFILE_NAME.format(id=net_id) + try: + mgr = self.connect(host, username, password) + if self._pp_domains_supported and self._virtual_fabric_enabled: + self.remove_port_profile_from_domain(mgr, domain_name, name) + 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(_LE("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(_LE("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(_LE("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) + + def remove_port_profile_from_domain(self, mgr, domain_name, name): + """Remove port-profile from default domain.""" + confstr = template.REMOVE_PORTPROFILE_FROM_DOMAIN.format( + domain_name=domain_name, name=name) + mgr.edit_config(target='running', config=confstr) + + def configure_port_profile_in_domain(self, mgr, domain_name, name): + """put port-profile in default domain.""" + confstr = template.CONFIGURE_PORTPROFILE_IN_DOMAIN.format( + domain_name=domain_name, name=name) + mgr.edit_config(target='running', config=confstr) + + def configure_l2_mode_for_vlan_profile_with_domains(self, mgr, name): + """Configures L2 mode for VLAN sub-profile.""" + confstr = template.CONFIGURE_L2_MODE_FOR_VLAN_PROFILE_IN_DOMAIN.format( + name=name) + mgr.edit_config(target='running', config=confstr) + + def nos_version_request(self, mgr): + """Get firmware information using NETCONF rpc.""" + reply = mgr.dispatch(template.SHOW_FIRMWARE_VERSION, None, None) + et = ElementTree.fromstring(str(reply)) + return et.find(template.NOS_VERSION).text + + def virtual_fabric_info(self, mgr): + """Get virtual fabric info using NETCONF get-config.""" + response = mgr.get_config('running', + filter=("xpath", "/vcs/virtual-fabric")) + et = ElementTree.fromstring(str(response)) + vfab_enable = et.find(template.VFAB_ENABLE) + if vfab_enable is not None: + return "enabled" + return "disabled" + + def create_svi(self, host, username, password, + rbridge_id, vlan_id, ip_address, router_id): + """create svi on configured rbridge-id.""" + try: + mgr = self.connect(host, username, password) + self.bind_vrf_to_svi(host, username, password, + rbridge_id, vlan_id, router_id) + self.configure_svi_with_ip_address(mgr, + rbridge_id, vlan_id, ip_address) + self.activate_svi(mgr, rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error: %s"), ex) + self.close_session() + + def delete_svi(self, host, username, password, + rbridge_id, vlan_id, gw_ip, router_id): + """delete svi from configured rbridge-id.""" + try: + mgr = self.connect(host, username, password) + self.remove_svi(mgr, rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error: %s"), ex) + self.close_session() + + def create_router(self, host, username, password, rbridge_id, router_id): + """create vrf and associate vrf.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + rd = "".join(i for i in router_id if i in "0123456789") + rd = rd[:4] + ":" + rd[:4] + try: + mgr = self.connect(host, username, password) + self.create_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + try: + # For Nos5.0.0 + self.configure_rd_for_vrf(mgr, rbridge_id, vrf_name, rd) + self.configure_address_family_for_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + try: + # This is done because on 4.0.0 rd doesnt accept alpha + # character nor hyphen + rd = "".join(i for i in router_id if i in "0123456789") + rd = rd[:4] + ":" + rd[:4] + self.configure_rd_for_vrf(mgr, rbridge_id, vrf_name, rd) + self.configure_address_family_for_vrf_v1(mgr, + rbridge_id, + vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + ctxt.reraise = False + + def delete_router(self, host, username, password, rbridge_id, router_id): + """delete router and associated vrf.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.delete_vrf(mgr, rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def bind_vrf_to_svi(self, host, username, password, rbridge_id, + vlan_id, router_id): + """binds vrf to a svi.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.add_vrf_to_svi(mgr, rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def unbind_vrf_to_svi(self, host, username, password, rbridge_id, + vlan_id, router_id): + """unbind vrf from the svi.""" + router_id = router_id[0:11] + vrf_name = template.OS_VRF_NAME.format(id=router_id) + try: + mgr = self.connect(host, username, password) + self.delete_vrf_from_svi(mgr, rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def create_vrf(self, mgr, rbridge_id, vrf_name): + """create vrf on rbridge.""" + confstr = template.CREATE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def delete_vrf(self, mgr, rbridge_id, vrf_name): + """delete vrf on rbridge.""" + + confstr = template.DELETE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_rd_for_vrf(self, mgr, rbridge_id, vrf_name, rd): + """configure rd on vrf on rbridge.""" + + confstr = template.CONFIGURE_RD_FOR_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name, + rd=rd) + mgr.edit_config(target='running', config=confstr) + + def configure_address_family_for_vrf_v1(self, mgr, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF_V1.format( + rbridge_id=rbridge_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_address_family_for_vrf(self, mgr, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF.format( + rbridge_id=rbridge_id, vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def configure_svi_with_ip_address(self, mgr, rbridge_id, + vlan_id, ip_address): + """configure SVI with ip address on rbridge.""" + + confstr = template.CONFIGURE_SVI_WITH_IP_ADDRESS.format( + rbridge_id=rbridge_id, + vlan_id=vlan_id, + ip_address=ip_address) + + mgr.edit_config(target='running', config=confstr) + + def activate_svi(self, mgr, rbridge_id, vlan_id): + """activate the svi on the rbridge.""" + confstr = template.ACTIVATE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + mgr.edit_config(target='running', config=confstr) + + def add_vrf_to_svi(self, mgr, rbridge_id, vlan_id, vrf_name): + """add vrf to svi on rbridge.""" + confstr = template.ADD_VRF_TO_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def delete_vrf_from_svi(self, mgr, rbridge_id, vlan_id, vrf_name): + """delete vrf from svi on rbridge.""" + confstr = template.DELETE_VRF_FROM_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + mgr.edit_config(target='running', config=confstr) + + def remove_svi(self, mgr, rbridge_id, vlan_id): + """delete vrf from svi on rbridge.""" + confstr = template.DELETE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + mgr.edit_config(target='running', config=confstr) diff --git a/networking_brocade/vdx/ampp/tests/__init__.py b/networking_brocade/vdx/ampp/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/tests/unit/__init__.py b/networking_brocade/vdx/ampp/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/tests/unit/ml2/__init__.py b/networking_brocade/vdx/ampp/tests/unit/ml2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/__init__.py b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/__init__.py b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py new file mode 100644 index 0000000..801b30b --- /dev/null +++ b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py @@ -0,0 +1,57 @@ +# Copyright (c) 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. +# +# + +import mock +from networking_brocade._i18n import _LI +from neutron.db import api as db +from neutron.tests.unit.extensions import test_l3 +from oslo_config import cfg +from oslo_context import context as oslo_context +from oslo_log import log as logging +from oslo_utils import importutils + +LOG = logging.getLogger(__name__) +L3_SVC_PLUGIN = ('neutron.services.l3_router.' + 'brocade.l3_router_plugin.BrocadeSVIPlugin') + + +class BrocadeSVIPlugin_TestCases(test_l3.TestL3NatBasePlugin): + + def setUp(self): + + def mocked_brocade_init(self): + LOG.debug("brocadeSVIPlugin::mocked_brocade_init()") + + self._switch = {'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'rbridge_id': cfg.CONF.ml2_brocade.rbridge_id + } + LOG.info(_LI("rbridge id %s"), self._switch['rbridge_id']) + self._driver = mock.MagicMock() + + self.l3_plugin = importutils.import_object(L3_SVC_PLUGIN) + with mock.patch.object(self.l3_plugin, + 'brocade_init', new=mocked_brocade_init): + super(BrocadeSVIPlugin_TestCases, self).setUp() + self.context = oslo_context.get_admin_context() + self.context.session = db.get_session() + + +class TestBrocadeSVINatBase(test_l3.L3NatExtensionTestCase, + BrocadeSVIPlugin_TestCases): + pass diff --git a/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py new file mode 100644 index 0000000..23d038e --- /dev/null +++ b/networking_brocade/vdx/ampp/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py @@ -0,0 +1,118 @@ +# Copyright (c) 2016 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 networking_brocade.vdx.ml2driver import ( + mechanism_brocade as brocademechanism) +from neutron.plugins.ml2 import config as ml2_config +from neutron.tests.unit.plugins.ml2 import test_plugin +from oslo_log import log as logging +from oslo_utils import importutils + +LOG = logging.getLogger(__name__) + +MECHANISM_NAME = ('networking_brocade.' + 'vdx.ml2driver.mechanism_brocade.BrocadeMechanism') + + +class TestBrocadeMechDriverV2(test_plugin.Ml2PluginV2TestCase): + + """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') + + 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_plugin.TestMl2NetworksV2, + TestBrocadeMechDriverV2): + pass + + +class TestBrocadeMechDriverPortsV2(test_plugin.TestMl2PortsV2, + TestBrocadeMechDriverV2): + pass + + +class TestBrocadeMechDriverSubnetsV2(test_plugin.TestMl2SubnetsV2, + TestBrocadeMechDriverV2): + pass + + +class TestBrocadeMechDriverFeaturesEnabledTestCase(TestBrocadeMechDriverV2): + + def setUp(self): + super(TestBrocadeMechDriverFeaturesEnabledTestCase, self).setUp() + + def test_version_features(self): + + vf = True + # Test for NOS version 4.0.3 + self.mechanism_driver.set_features_enabled("4.0.3", vf) + # Verify + pp_domain_support, virtual_fabric_enabled = ( + self.mechanism_driver.get_features_enabled() + ) + self.assertFalse(pp_domain_support) + self.assertTrue(virtual_fabric_enabled) + + # Test for NOS version 4.1.0 + vf = True + self.mechanism_driver.set_features_enabled("4.1.0", vf) + # Verify + pp_domain_support, virtual_fabric_enabled = ( + self.mechanism_driver.get_features_enabled() + ) + self.assertTrue(pp_domain_support) + self.assertTrue(virtual_fabric_enabled) + + # Test for NOS version 4.1.3 + vf = False + self.mechanism_driver.set_features_enabled("4.1.3", vf) + # Verify + pp_domain_support, virtual_fabric_enabled = ( + self.mechanism_driver.get_features_enabled() + ) + self.assertTrue(pp_domain_support) + self.assertFalse(virtual_fabric_enabled) + + # Test for NOS version 5.0.0 + vf = True + self.mechanism_driver.set_features_enabled("5.0.0", vf) + # Verify + pp_domain_support, virtual_fabric_enabled = ( + self.mechanism_driver.get_features_enabled() + ) + self.assertTrue(pp_domain_support) + self.assertTrue(virtual_fabric_enabled) diff --git a/networking_brocade/vdx/bare_metal/__init__.py b/networking_brocade/vdx/bare_metal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/bare_metal/mechanism_brocade.py b/networking_brocade/vdx/bare_metal/mechanism_brocade.py new file mode 100644 index 0000000..111e67e --- /dev/null +++ b/networking_brocade/vdx/bare_metal/mechanism_brocade.py @@ -0,0 +1,200 @@ +# 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. + + +"""Implentation of Brocade ML2 Mechanism driver for ML2 Plugin.""" + +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.bare_metal import util as baremetal_util +from networking_brocade.vdx.non_ampp.ml2driver.nos import nosdriver as driver +from neutron.common import constants as n_const +from neutron.extensions import portbindings +from neutron.plugins.ml2 import driver_api as api +from oslo_config import cfg +from oslo_log import helpers as log_helpers +try: + from oslo_log import log as logging +except ImportError: + from neutron.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + +NOS_DRIVER = 'networking_brocade.vdx.non_ampp.ml2driver' +'.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=_('OS Type of the switch')), + cfg.StrOpt('osversion', default='4.0.0', + help=_('OS Version number')) + ] + +cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade") + + +class BrocadeMechanism(api.MechanismDriver): + """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() + self._driver.close_session() + + def brocade_init(self): + """Brocade specific initialization for this class.""" + + osversion = None + self._switch = { + 'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'ostype': cfg.CONF.ml2_brocade.ostype, + 'osversion': cfg.CONF.ml2_brocade.osversion} + + self._driver = driver.NOSdriver(self._switch['address'], + self._switch['username'], + self._switch['password']) + + # Detect version of NOS on the switch + osversion = self._switch['osversion'] + if osversion == "autodetect": + osversion = self._driver.get_nos_version( + self._switch['address'], + self._switch['username'], + self._switch['password']) + self._driver.close_session() + + def create_network_precommit(self, mech_context): + """Create Network in the mechanism specific database table.""" + + def create_network_postcommit(self, mech_context): + """Create Network as a portprofile on the switch.""" + + def delete_network_precommit(self, mech_context): + """Delete Network from the plugin specific database table.""" + + def delete_network_postcommit(self, mech_context): + """Delete network. + + This translates to removng portprofile + from the switch. + """ + + def update_network_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def update_network_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + + def create_port_precommit(self, mech_context): + """Create logical port on the switch (db update).""" + + def create_port_postcommit(self, mech_context): + """Associate the assigned MAC address to the portprofile.""" + + def delete_port_precommit(self, mech_context): + """Delete logical port on the switch (db update).""" + + def delete_port_postcommit(self, mech_context): + """Dissociate VLAN from baremetal connected + port. + """ + LOG.debug(("brocade_baremetal delete_port_postcommit(self: called")) + port = mech_context.current + if baremetal_util.is_baremetal_deploy(port): + params = baremetal_util.validate_physical_net_params(mech_context) + try: + # TODO(rmadapur): Handle local_link_info portgroups + for i in params["local_link_information"]: + speed, name = i['port_id'] + self._driver.remove_native_vlan_from_interface(speed, name) + except Exception: + LOG.exception(_LE("Brocade NOS driver:failed to remove native" + " vlan from bare metal interface")) + raise Exception(_("NOS driver:failed to remove native vlan")) + + def update_port_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def update_port_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + + @log_helpers.log_method_call + def bind_port(self, context): + port = context.current + vnic_type = port['binding:vnic_type'] + + LOG.debug("Brcd:Attempting to bind port %(port)s with vnic_type " + "%(vnic_type)s on network %(network)s", + {'port': port['id'], 'vnic_type': vnic_type, + 'network': context.network.current['id']}) + + if baremetal_util.is_baremetal_deploy(port): + segments = context.segments_to_bind + LOG.info(_LI("Segments:%s"), segments) + params = baremetal_util.validate_physical_net_params(context) + try: + # TODO(rmadapur): Handle local_link_info portgroups + for i in params["local_link_information"]: + speed, name = i['port_id'] + vlan_id = segments[0][api.SEGMENTATION_ID] + self._driver.configure_native_vlan_on_interface( + speed, + name, vlan_id) + except Exception: + LOG.exception(_LE("Brocade NOS driver:failed to trunk" + " bare metal vlan")) + raise Exception(_("Brocade switch exception:" + " bind_port failed for baremetal")) + context.set_binding(segments[0][api.ID], + portbindings.VIF_TYPE_OTHER, {}, + status=n_const.PORT_STATUS_ACTIVE) + + def create_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def create_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + + def delete_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def delete_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + + def update_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def update_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" diff --git a/networking_brocade/vdx/bare_metal/util.py b/networking_brocade/vdx/bare_metal/util.py new file mode 100644 index 0000000..349ee28 --- /dev/null +++ b/networking_brocade/vdx/bare_metal/util.py @@ -0,0 +1,235 @@ +# 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. + +from neutron.extensions import portbindings +from neutron.plugins.ml2.common import exceptions as ml2_exc +from neutron.plugins.ml2 import driver_api +import oslo_i18n +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +import re + +LOG = logging.getLogger(__name__) +RANGE_DEFINITION = re.compile(r'(\d)-(\d)') +_translators = oslo_i18n.TranslatorFactory(domain="fj") +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical + + +def eliminate_val(definition, target): + """Eliminate specified value from range value. + + @param definition a string of range definition separated with "," + ex. "1,2,3" or "1-5" + @param target an integer of the target to eliminate + @return eliminated a string of eliminated value + """ + if definition is None: + return [] + targets = definition.split(',') + rejected = targets + val = str(target) + LOG.info(_LI("Before rejected:%s"), targets) + for t in targets: + m = RANGE_DEFINITION.match(t) + if m: + low = m.group(1) + high = m.group(2) + if val in t: + rejected.remove(t) + # matches the lowest one + if (val == low): + # Case: definition is "1-2" and target is "1" + if ((int(val) + 1) == int(high)): + rejected.append(high) + else: + rejected.append(str(int(val) + 1) + "-" + high) + LOG.info(_LI("Rejected result:%s"), rejected) + return ','.join(rejected) + # matches the highest one + else: + # Ex. definition is "1-2" and target is "2" + if ((int(val) - 1) == int(low)): + rejected.append(low) + else: + rejected.append(low + "-" + str(int(val) - 1)) + LOG.info(_LI("Rejected result:%s"), rejected) + return ','.join(rejected) + # matches between lower one and higher one + elif (int(low) < int(val) and int(val) < int(high)): + rejected.remove(t) + # Ex. definition is "1-n" and target is "2" + if ((int(val) - 1) == int(low)): + rejected.append(low) + # Ex. definition is "1-3" and target is "2" + if ((int(val) + 1) == int(high)): + rejected.append(high) + # Ex. definition is "1-4" and target is "2" + else: + rejected.append(str(int(val) + 1) + "-" + high) + # Ex. definition is "n-5" and target is "4"(n is NOT "3") + elif ((int(val) + 1) == int(high)): + rejected.append(high) + rejected.append(low + "-" + str(int(val) - 1)) + # Ex. definition is "1-5" and target is "3" + else: + rejected.append(low + "-" + str(int(val) - 1)) + rejected.append(str(int(val) + 1) + "-" + high) + LOG.info(_LI("Rejected result:%s"), rejected) + return ','.join(rejected) + elif val == t: + rejected.remove(t) + LOG.info(_LI('Rejected result:%s'), rejected) + return ','.join(rejected) + LOG.info(_LI('target for eliminate doesn\'t exist.')) + return ','.join(rejected) + + +def get_network_segments(network): + """Get network_type and segmentation_id from specified network. + + @param network a network object + @return network_type a string of network type(ex. "vlan" or "vxlan") + segmentation_id a integer of segmentation_id + """ + + _validate_network(network) + segment = network.network_segments[0] + network_type = segment[driver_api.NETWORK_TYPE] + segmentation_id = segment[driver_api.SEGMENTATION_ID] + LOG.info(_LI("network_type = %s") % network_type) + LOG.info(_LI("segmentation_id = %s") % segmentation_id) + return network_type, segmentation_id + + +def _get_long_speed(short_speed): + if 'Te' in short_speed: + return "tengigabitethernet" + + elif 'Gi' in short_speed: + return "gigabitethernet" + + elif 'Fo' in short_speed: + return "fortyGigabitEthernet" + + elif 'Hu' in short_speed: + return "hundredGigabitEthernet" + else: + return "unknown" + + +def get_physical_connectivity(port): + """Get local_link_information from specified port. + + @param port a port object + @return lli a list of following dict + {"switch_id": "MAC_of_switch", "port_id": "Te:1/0/1", + "switch_info": "switch_name"} + """ + + link_infos = [] + binding_profile = port['binding:profile'] + lli = binding_profile.get("local_link_information", {}) + is_all_specified = True if lli else False + for i in lli: + if not (i.get('switch_id') and i.get('port_id') and + i.get('switch_info')): + is_all_specified = False + + else: + p = i.get('port_id') + speed, port = p.split(':') + speed = _get_long_speed(speed) + speed_port = (speed, port) + i['port_id'] = speed_port + link_infos.append(i) + + if is_all_specified: + return link_infos + LOG.error(_LE("Some physical network param is missing:%s"), lli) + raise ml2_exc.MechanismDriverError(method="get_physical_connectivity") + + +def is_baremetal_deploy(port): + """Judge a specified port is for baremetal or not. + + @param port a port object + @return True/False a boolean baremetal:True, otherwise:False + """ + + vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL) + if (vnic_type == portbindings.VNIC_BAREMETAL): + return True + else: + return False + + +def is_lag(local_link_information): + """Judge a specified port param is for LAG(linkaggregation) or not. + + @param local_link_information a list of dict + @return True/False a boolean LAG:True, otherwise:False + """ + + return True if len(local_link_information) > 1 else False + + +def _validate_network(network): + """Validate network parameter(network_type and segmentation_id). + + @param a network object + @return None if both network_type and segmentation_id are included + """ + + segment = network.network_segments[0] + vlan_id = segment[driver_api.SEGMENTATION_ID] + if (segment[driver_api.NETWORK_TYPE] == 'vlan' and vlan_id): + return + LOG.error(_LE("Fujitsu Mechanism: only network type vlan is supported")) + raise ml2_exc.MechanismDriverError(method="_validate_network_type") + + +@log_helpers.log_method_call +def validate_physical_net_params(mech_context): + """Validate physical network parameters for baremetal deployment. + + Validates network & port params and returns dictionary. + 'local_link_information' is a dictionary from Ironic-port. This value + includes as follows: + 'switch_id': A string of switch's MAC address + This value is equal to 'chassis_id' from LLDP TLV. + 'port_id': A string of switch interface name. + This value is equal to 'port_id' from LLDP TLV. + 'switch_info': A string of switch name. + This value is equal to 'system_name' from LLDP TLV. + + @param mech_context a Context instance + @return A dictionary parameters for baremetal deploy + """ + + port = mech_context.current + _validate_network(mech_context.network) + + # currently supports only one segment per network + segment = mech_context.network.network_segments[0] + vlan_id = segment[driver_api.SEGMENTATION_ID] + local_link_information = get_physical_connectivity(port) + return { + "local_link_information": local_link_information, + "vlan_id": vlan_id, + "lag": is_lag(local_link_information) + } diff --git a/networking_brocade/vdx/db/__init__.py b/networking_brocade/vdx/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/db/migration/__init__.py b/networking_brocade/vdx/db/migration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/__init__.py b/networking_brocade/vdx/db/migration/alembic_migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/env.py b/networking_brocade/vdx/db/migration/alembic_migrations/env.py new file mode 100644 index 0000000..30120e9 --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/env.py @@ -0,0 +1,123 @@ +# Copyright (c) 2015 Brocade Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from logging.config import fileConfig + +from alembic import context +from oslo_config import cfg +from oslo_db.sqlalchemy import session +import sqlalchemy as sa +from sqlalchemy import event + +from neutron.db.migration.alembic_migrations import external +from neutron.db.migration.models import head # noqa +from neutron.db import model_base + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config +neutron_config = config.neutron_config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = model_base.BASEV2.metadata + +MYSQL_ENGINE = None +BROCADE_VERSION_TABLE = 'brocade_alembic_version' + + +def set_mysql_engine(): + try: + mysql_engine = neutron_config.command.mysql_engine + except cfg.NoSuchOptError: + mysql_engine = None + + global MYSQL_ENGINE + MYSQL_ENGINE = (mysql_engine or + model_base.BASEV2.__table_args__['mysql_engine']) + + +def include_object(object, name, type_, reflected, compare_to): + if type_ == 'table' and name in external.TABLES: + return False + else: + return True + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL or an Engine. + + Calls to context.execute() here emit the given string to the + script output. + + """ + set_mysql_engine() + + kwargs = dict() + if neutron_config.database.connection: + kwargs['url'] = neutron_config.database.connection + else: + kwargs['dialect_name'] = neutron_config.database.engine + kwargs['include_object'] = include_object + kwargs['version_table'] = BROCADE_VERSION_TABLE + context.configure(**kwargs) + + with context.begin_transaction(): + context.run_migrations() + + +@event.listens_for(sa.Table, 'after_parent_attach') +def set_storage_engine(target, parent): + if MYSQL_ENGINE: + target.kwargs['mysql_engine'] = MYSQL_ENGINE + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + set_mysql_engine() + engine = session.create_engine(neutron_config.database.connection) + + connection = engine.connect() + context.configure( + connection=connection, + target_metadata=target_metadata, + include_object=include_object, + version_table=BROCADE_VERSION_TABLE, + ) + + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + engine.dispose() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEAD.bk b/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEAD.bk new file mode 100644 index 0000000..638ff42 --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEAD.bk @@ -0,0 +1 @@ +4fadb44a1e2d \ No newline at end of file diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEADS b/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEADS new file mode 100644 index 0000000..65e26ca --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/HEADS @@ -0,0 +1,2 @@ +a84d6a05d397 +4fadb44a1e2d diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/kilo_release.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/kilo_release.py new file mode 100644 index 0000000..90ee786 --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/kilo_release.py @@ -0,0 +1,27 @@ +# 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. +# + +"""kilo + +Revision ID: kilo +Revises: start_networking_brcd_clos +Create Date: 2015-04-16 00:00:00.000000 + +""" +# revision identifiers, used by Alembic. +revision = 'kilo' +down_revision = 'start_ml2_brcd' + + +def upgrade(): + pass diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/4fadb44a1e2d_ml2_brcd_contract.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/4fadb44a1e2d_ml2_brcd_contract.py new file mode 100644 index 0000000..3aa9568 --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/4fadb44a1e2d_ml2_brcd_contract.py @@ -0,0 +1,35 @@ +# Copyright 2016 Brocade Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""ml2_brcd_contract + +Revision ID: 4fadb44a1e2d +Revises: 30d703af31fb +Create Date: 2016-02-09 15:30:27.535472 + +""" + +from neutron.db.migration import cli + +# revision identifiers, used by Alembic. +revision = '4fadb44a1e2d' +down_revision = 'kilo' +branch_labels = None +depends_on = None +branch_labels = (cli.CONTRACT_BRANCH,) + + +def upgrade(): + pass diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/__init__.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/contract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/__init__.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/a84d6a05d397_ml2_brcd.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/a84d6a05d397_ml2_brcd.py new file mode 100644 index 0000000..a00fb98 --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/liberty/expand/a84d6a05d397_ml2_brcd.py @@ -0,0 +1,53 @@ +# Copyright 2016 Brocade Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""ml2_brcd + +Revision ID: a84d6a05d397 +Revises: kilo +Create Date: 2016-02-09 14:56:58.752583 + +""" +from alembic import op +from neutron.db.migration import cli +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'a84d6a05d397' +down_revision = 'kilo' +branch_labels = None +depends_on = None +branch_labels = (cli.EXPAND_BRANCH,) + + +def upgrade(): + + op.create_table('ml2_brocadesvis', + sa.Column('tenant_id', sa.String( + length=255), nullable=True), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('svi_id', sa.String(length=36), nullable=False), + sa.Column('admin_state_up', sa.Boolean(), nullable=False), + sa.Column('ip_address', sa.String( + length=36), nullable=True), + sa.Column('subnet_mask', sa.String( + length=36), nullable=True), + sa.PrimaryKeyConstraint('id', 'svi_id'), + mysql_engine='InnoDB' + ) + op.create_index(op.f('ix_ml2_brocadesvis_tenant_id'), + 'ml2_brocadesvis', ['tenant_id'], unique=False) + op.add_column('ml2_brocadeports', sa.Column( + 'host', sa.String(length=255), nullable=True)) diff --git a/networking_brocade/vdx/db/migration/alembic_migrations/versions/start_networking_brocade_migration.py b/networking_brocade/vdx/db/migration/alembic_migrations/versions/start_networking_brocade_migration.py new file mode 100644 index 0000000..6a4589a --- /dev/null +++ b/networking_brocade/vdx/db/migration/alembic_migrations/versions/start_networking_brocade_migration.py @@ -0,0 +1,34 @@ +# opyright 2015 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. +# + +"""start networking-brocade chain + +Revision ID: start_networking_brocade_migration +Revises: None +Create Date: 2015-02-04 11:06:18.196062 + +""" + +# revision identifiers, used by Alembic. +revision = 'start_ml2_brcd' +down_revision = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/networking_brocade/vdx/db/models.py b/networking_brocade/vdx/db/models.py new file mode 100644 index 0000000..f6bc925 --- /dev/null +++ b/networking_brocade/vdx/db/models.py @@ -0,0 +1,220 @@ +# Copyright 2016 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) +# Ritesh Madapurath (rmadapur@brocade.com) +# Raghuprem Muthigi (rmuthigi@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)) + host = sa.Column(sa.String(255)) + + +class ML2_BrocadeSvi(model_base.BASEV2, models_v2.HasId, + models_v2.HasTenant): + + """schema for brocade svi """ + svi_id = sa.Column(sa.String(36), primary_key=True) + admin_state_up = sa.Column(sa.Boolean, nullable=False) + ip_address = sa.Column(sa.String(36)) + subnet_mask = sa.Column(sa.String(36)) + + +def create_svi(context, router_id, tenant_id, svi, + admin_state_up, ip_address, net_mask): + """create svi port """ + session = context.session + with session.begin(subtransactions=True): + ve = get_svi(context, router_id, tenant_id, svi, ip_address, net_mask) + if not ve: + svi = ML2_BrocadeSvi(id=router_id, tenant_id=tenant_id, + svi_id=svi, admin_state_up=admin_state_up, + ip_address=ip_address, + subnet_mask=net_mask) + session.add(svi) + return svi + + +def delete_svi(context, router_id, tenant_id, svi, ip_address, net_mask): + """Delete a brocade specific network/port-profiles.""" + + session = context.session + with session.begin(subtransactions=True): + svi = get_svi(context, router_id, tenant_id, svi, ip_address, net_mask) + if svi: + session.delete(svi) + + +def get_svi(context, router_id, tenant_id, svi, ip_address, net_mask): + session = context.session + return session.query(ML2_BrocadeSvi).filter_by(id=router_id, + tenant_id=tenant_id, + svi_id=svi, + ip_address=ip_address, + subnet_mask=net_mask).\ + first() + + +def get_svis(context, router_id, tenant_id): + session = context.session + return session.query(ML2_BrocadeSvi).filter_by(id=router_id, + tenant_id=tenant_id).all() + + +def get_list_svi_ids(context, router_id, tenant_id): + ves = [] + svis = get_svis(context, router_id, tenant_id) + for svi in svis: + if svi['svi_id']: + ves.append(svi['svi_id']) + return ves + + +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=network_type, + tenant_id=tenant_id) + session.add(net) + return net + + +def delete_network(context, net_id): + """Delete a brocade specific network""" + + 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, host): + """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, + host=host) + 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 is_vm_exists_on_host(context, host, physnet, vlan_id): + """check if port is tagged on host""" + session = context.session + qc = session.query(ML2_BrocadePort).filter_by( + physical_interface=physnet, host=host, vlan_id=vlan_id).count() + return qc > 1 + + +def is_last_vm_on_host(context, host, physnet, vlan_id): + """check if port is tagged on host""" + session = context.session + qc = session.query(ML2_BrocadePort).filter_by( + physical_interface=physnet, host=host, vlan_id=vlan_id).count() + return qc <= 0 + + +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/networking_brocade/vdx/ml2driver/mechanism_brocade.py b/networking_brocade/vdx/ml2driver/mechanism_brocade.py index 19daa5f..45e9789 100644 --- a/networking_brocade/vdx/ml2driver/mechanism_brocade.py +++ b/networking_brocade/vdx/ml2driver/mechanism_brocade.py @@ -16,8 +16,9 @@ """Implentation of Brocade ML2 Mechanism driver for ML2 Plugin.""" -from neutron.i18n import _LE -from neutron.i18n import _LI +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI from neutron.plugins.ml2 import driver_api from oslo_config import cfg from oslo_log import log as logging diff --git a/networking_brocade/vdx/ml2driver/nos/nosdriver.py b/networking_brocade/vdx/ml2driver/nos/nosdriver.py index ea65a7b..a9645da 100644 --- a/networking_brocade/vdx/ml2driver/nos/nosdriver.py +++ b/networking_brocade/vdx/ml2driver/nos/nosdriver.py @@ -24,8 +24,9 @@ from oslo_utils import excutils from xml.etree import ElementTree +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE from networking_brocade.vdx.ml2driver.nos import nctemplates as template -from neutron.i18n import _LE LOG = logging.getLogger(__name__) SSH_PORT = 22 diff --git a/networking_brocade/vdx/non_ampp/__init__.py b/networking_brocade/vdx/non_ampp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/non_ampp/ml2driver/__init__.py b/networking_brocade/vdx/non_ampp/ml2driver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_driver.py b/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_driver.py new file mode 100644 index 0000000..727e881 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_driver.py @@ -0,0 +1,293 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2016 Brocade Networks 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. +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LW +from networking_brocade.vdx.non_ampp.ml2driver.nos import ( + nctemplates as template) +from networking_brocade.vdx.non_ampp.ml2driver.nos import ( + nosdriver as driver) +from networking_brocade.vdx.non_ampp.ml2driver import utils +from neutron_fwaas.services.firewall.drivers import fwaas_base +import os.path +from oslo_log import log as logging +from oslo_serialization import jsonutils + +ACL_BATCH_SIZE = 510 +LOG = logging.getLogger(__name__) + + +class BrocadeFwaasDriver(fwaas_base.FwaasDriverBase): + + def __init__(self): + LOG.debug("Initializing fwaas Brocade driver") + self._driver = None + self._seq_id_low = None + self._seq_id_high = None + self.brocade_init() + + def brocade_init(self): + """Brocade specific initialization.""" + LOG.debug("brocade init BrocadeFwaas Drivers") + self._switch = utils.get_brocade_credentials() + self._svi = utils.get_brocade_l3_config() + self._switch['rbridge_ids'] = self._svi['rbridge_ids'] + self._fwaas = utils.get_brocade_fwaas_config() + LOG.debug("FWAAS PARAMETERS seq_ids %s direction %s count %s" + " log %s", self._fwaas['seq_ids'], + self._fwaas['direction'], + self._fwaas['count'], + self._fwaas['log']) + + if not ((self._fwaas['direction'] == 'both') or + (self._fwaas['direction'] == 'in') or + (self._fwaas['direction'] == 'out')): + LOG.warning(_LW("invalid direction %s intializing" + " todirection both"), + self._fwaas['direction']) + self._fwaas['direction'] = 'both' + self._seq_id_low, self._seq_id_high = utils.get_seq_ids( + self._fwaas['seq_ids']) + self.seq_id_bm = utils.SeqIdBitmap(int(self._seq_id_low), + int(self._seq_id_high)) + self._driver = driver.NOSdriver(self._switch['address'], + self._switch['username'], + self._switch['password']) + self._pre_acls, self._post_acls = self.open_file_if_exists( + self._fwaas['acl_file']) + self.req = [] + self._driver.close_session() + + def open_file_if_exists(self, fname): + pre_acls = [] + post_acls = [] + if os.path.isfile(fname): + with open(fname, "r") as acl_file: + try: + data = jsonutils.load(acl_file) + if 'pre_acls' in data: + pre_acls = data['pre_acls'] + if 'post_acls' in data: + post_acls = data['post_acls'] + LOG.debug("pre acls : %s", pre_acls) + LOG.debug("post acls : %s", post_acls) + except Exception: + LOG.warning(_LW("Error Loadng %s file(may be empty file)"), + fname) + return pre_acls, post_acls + else: + LOG.warning(_LW("%s file doesn't exists"), fname) + + return pre_acls, post_acls + + def create_firewall(self, apply_list, firewall): + LOG.debug('create_firewall (%s)', firewall['id']) + # update firewall would take lock so no lock here + return self.update_firewall(apply_list, firewall) + + def update_firewall(self, apply_list, firewall): + LOG.debug("update_firewall (%s)", firewall['id']) + + if firewall['admin_state_up']: + return self._update_firewall(apply_list, firewall) + else: + return self.apply_default_policy(apply_list, firewall) + + def delete_firewall(self, apply_list, firewall): + LOG.debug("delete_firewall (%s)", firewall['id']) + + return self.apply_default_policy(apply_list, firewall) + + def apply_default_policy(self, apply_list, firewall): + LOG.debug("apply_default_policy (%s)", firewall['id']) + + self._clear_policy(apply_list, firewall) + return True + + def _update_firewall(self, apply_list, firewall): + LOG.debug("Updating firewall (%s)", firewall['id']) + self._clear_policy(apply_list, firewall) + try: + self._setup_policy(apply_list, firewall) + except Exception as e: + LOG.error(_LE("_update_firewall::Error creating ACL policy :" + "Error: %s"), e) + raise e + return True + + def _apply_policy_on_interface(self, policy_name, svi): + LOG.debug("brocade_fwaas:_setup_policy svi %s", svi) + if(self._fwaas['direction'] == 'both' or + self._fwaas['direction'] == 'in'): + for rbridge_id in self._switch['rbridge_ids']: + self._driver.configure_policy_on_interface(rbridge_id, + svi, + policy_name, + "in") + if(self._fwaas['direction'] == 'both' or + self._fwaas['direction'] == 'out'): + for rbridge_id in self._switch['rbridge_ids']: + self._driver.configure_policy_on_interface(rbridge_id, + svi, + policy_name, + "out") + + def _is_policy_exists(self, policy_name): + return self._driver.is_ip_acl_exists(policy_name) + + def merge_and_replay_acls(self, name): + if self.req: + all_rules = "".join(self.req) + acl_header = template.IP_ACL_RULE_BULKING_START.format(name=name) + acl_footer = template.IP_ACL_RULE_BULKING_END.format() + acl_netconf = acl_header + all_rules + acl_footer + LOG.debug("merge_and_replay_acls netconf %s", acl_netconf) + self._driver.create_acl_rule(acl_netconf) + del self.req[:] + + def _config_replay_acls(self, policy_name, rule, seq_id): + try: + req = self._make_policy(policy_name, rule, seq_id) + self.req.append(req) + if len(self.req) >= ACL_BATCH_SIZE: + self.merge_and_replay_acls(policy_name) + except Exception as e: + LOG.error(_LE("error creating rule %s"), e) + raise e + return + + def _config_replay_acls_file(self, policy_name, acl_file, seq_ids, index): + for rule in acl_file: + rule = rule['acl'] + try: + self._config_replay_acls( + policy_name, rule, str(seq_ids[index])) + except Exception as e: + LOG.error(_LE("error _config_replay_acls_file %s"), e) + raise e + index = index + 1 + return index + + def _setup_policy(self, apply_list, fw): + # create zones no matter if they exist. Interfaces are added by router + policy_name = utils.get_firewall_object_prefix(fw) + num_seq_id = len(fw['firewall_rule_list']) + len(self._pre_acls) +\ + len(self._post_acls) + seq_ids = self.seq_id_bm.get_seq_ids(policy_name, num_seq_id) + index = 0 + try: + if not self._driver.is_ip_acl_exists(policy_name): + index = self._config_replay_acls_file(policy_name, + self._pre_acls, + seq_ids, index) + for rule in fw['firewall_rule_list']: + if not rule['enabled']: + continue + if rule['ip_version'] == 4: + self._config_replay_acls(policy_name, rule, + str(seq_ids[index])) + index = index + 1 + else: + LOG.warning(_LW("Unsupported IP version rule.")) + index = self._config_replay_acls_file(policy_name, + self._post_acls, + seq_ids, index) + self.merge_and_replay_acls(policy_name) + + for ri in apply_list: + for svi in ri.router['svis']: + self._apply_policy_on_interface(policy_name, svi) + except Exception as e: + LOG.error(_LE("Error creating ACL policy :Error: %s"), e) + self._clear_policy(apply_list, fw) + raise e + + def _clear_policy(self, apply_list, fw): + policy = utils.get_firewall_object_prefix(fw) + for ri in apply_list: + for svi in ri.router['svis']: + LOG.debug("brocade_fwaas:_clear_policy svi %s", svi) + if(self._fwaas['direction'] == 'both' or + self._fwaas['direction'] == 'in'): + for rbridge_id in self._switch['rbridge_ids']: + self._driver.remove_policy_on_interface(rbridge_id, + svi, + policy, + "in") + if(self._fwaas['direction'] == 'both' or + self._fwaas['direction'] == 'out'): + for rbridge_id in self._switch['rbridge_ids']: + self._driver.remove_policy_on_interface(rbridge_id, + svi, + policy, + "out") + + for rbridge_id in self._switch['rbridge_ids']: + self._driver.delete_policy(rbridge_id, policy) + + def _make_policy(self, name, rule, seq_id): + src_ip = 'any' + dst_ip = 'any' + src_mask = '' + dst_mask = '' + sport_operator = None + sport_low = None + sport_high = None + dport_operator = None + dport_low = None + dport_high = None + dscp = None + if rule.get('action') == 'allow': + action = 'permit' + else: + action = 'deny' + + protocol = rule.get('protocol') + if((protocol == 'any') or (protocol is None)): + protocol = 'ip' + if ((protocol == 'tcp') or (protocol == 'udp')): + if 'source_port' in rule: + sport = rule['source_port'] + sport_low, sport_high = utils.get_ports(sport) + sport_operator = utils.get_port_operator(sport_low, sport_high) + if 'destination_port' in rule: + dport = rule['destination_port'] + dport_low, dport_high = utils.get_ports(dport) + dport_operator = utils.get_port_operator(dport_low, + dport_high) + + src_address = rule.get('source_ip_address') or 'any' + if src_address != 'any': + src_ip, src_mask = utils.cidr_2_nwm(src_address) + dst_address = rule.get('destination_ip_address') or 'any' + if dst_address != 'any': + dst_ip, dst_mask = utils.cidr_2_nwm(dst_address) + + if 'dscp' in rule: + dscp = str(rule.get('dscp')) + try: + xml_request = utils.make_rule( + name, seq_id, action, protocol, src_ip, + src_mask, dst_ip, dst_mask, sport_operator, sport_low, + sport_high, dport_operator, dport_low, dport_high, + self._fwaas['count'], self._fwaas['log'], dscp) + except Exception as e: + LOG.error(_LE("error _make_policy %s"), e) + raise e + + LOG.debug("xml request %s", xml_request) + + return xml_request diff --git a/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_plugin.py b/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_plugin.py new file mode 100644 index 0000000..093f236 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/brocade_fwaas_plugin.py @@ -0,0 +1,230 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2016 Brocade Networks 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. +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.non_ampp.ml2driver import\ + brocade_fwaas_driver as fwaas_driver +from networking_brocade.vdx.non_ampp.ml2driver import fwaas_plugin as plugin +from networking_brocade.vdx.non_ampp.ml2driver import utils +from neutron import manager +from neutron.plugins.common import constants as const +from neutron.plugins.common import constants as plugin_constants +from neutron.plugins.ml2.drivers.brocade.db import models as brocade_db +import neutron_fwaas +from neutron_fwaas.extensions import firewall as fw_ext +from oslo_config import cfg +from oslo_log import log as logging +import threading +LOG = logging.getLogger(__name__) + + +class RouterInfo(object): + + def __init__(self, router): + self._router = router + + @property + def router(self): + return self._router + + +class BrocadeFirewallPlugin(plugin.FirewallPlugin): + + """Implementation of the Neutron Brocade Firewall Service Plugin. + This class manages fwass request and response with the help + fwaas_driver.BrocadeFwaasDriver + """ + supported_extension_aliases = ["fwaas"] + path_prefix = fw_ext.FIREWALL_PREFIX + + def __init__(self): + super(BrocadeFirewallPlugin, self).__init__() + ext_path = neutron_fwaas.extensions.__path__[0] + if ext_path not in cfg.CONF.api_extensions_path.split(':'): + cfg.CONF.set_override('api_extensions_path', + cfg.CONF.api_extensions_path + ':' + + ext_path) + self._fwaas_driver = fwaas_driver.BrocadeFwaasDriver() + self._lock = threading.Lock() + + def get_plugin_type(self): + return plugin_constants.FIREWALL + + def _is_l3_agent_running(self, context): + l3plugin = manager.NeutronManager.get_service_plugins()[ + plugin_constants.L3_ROUTER_NAT] + if not l3plugin: + LOG.error(_LE('No plugin for L3 routing registered! Will reply ' + 'no l3 agents!! ')) + return False + l3_agents = l3plugin.get_l3_agents(context, True) + LOG.info(_LI("List of L3 agents _is_l3_agent_running %s"), l3_agents) + return ((l3_agents) and (len(l3_agents) > 0)) + + def _get_routers(self, context): + """get all active routers for tenant""" + l3plugin = manager.NeutronManager.get_service_plugins()[ + plugin_constants.L3_ROUTER_NAT] + if not l3plugin: + routers = {} + LOG.error(_LE('No plugin for L3 routing registered! Will reply ' + 'to l3 agent with empty router dictionary.')) + return routers + routers = l3plugin._get_sync_routers(context) + for r in routers: + router_id = r['id'] + tenant_id = r['tenant_id'] + svis = brocade_db.get_list_svi_ids(context, router_id, tenant_id) + r['svis'] = svis + return routers + + def handle_router_interface_add(self, context, svi, tenant_id): + fw_list = self.get_firewalls(context) + for fw in fw_list: + fw = self._make_firewall_dict(fw) + policy_name = utils.get_firewall_object_prefix(fw) + if fw['tenant_id'] == tenant_id and\ + fw['status'] == const.ACTIVE: + try: + if not self._fwaas_driver._is_policy_exists(policy_name): + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, + fw['id'])) + self._invoke_driver_for_plugin_api(context, + fw_with_rules, + 'update_firewall') + else: + self._fwaas_driver._apply_policy_on_interface( + policy_name, svi) + except Exception as e: + LOG.error(_LE("Error adding Firewall rule to" + "interface %s "), e) + elif fw['tenant_id'] == tenant_id and\ + fw['status'] == const.PENDING_CREATE: + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, fw['id'])) + self._invoke_driver_for_plugin_api(context, fw_with_rules, + 'update_firewall') + self.endpoints[0].set_firewall_status(context, fw['id'], + const.ACTIVE) + + def create_firewall(self, context, firewall): + with self._lock: + fw = super(BrocadeFirewallPlugin, self).create_firewall( + context, firewall) + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, fw['id'])) + self._invoke_driver_for_plugin_api(context, fw_with_rules, + 'create_firewall') + return fw + + def update_firewall(self, context, id, firewall): + with self._lock: + fw = super(BrocadeFirewallPlugin, self).update_firewall(context, + id, + firewall) + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, fw['id'])) + self._invoke_driver_for_plugin_api(context, fw_with_rules, + 'update_firewall') + return fw + + def delete_firewall(self, context, id): + with self._lock: + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, id)) + super(BrocadeFirewallPlugin, self).delete_firewall(context, id) + self._invoke_driver_for_plugin_api(context, fw_with_rules, + 'delete_firewall') + self.endpoints[0].firewall_deleted(context, id) + + def _rpc_update_firewall(self, context, firewall_id): + with self._lock: + super(BrocadeFirewallPlugin, self).\ + _rpc_update_firewall(context, + firewall_id) + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, + firewall_id)) + self._invoke_driver_for_plugin_api(context, + fw_with_rules, + 'update_firewall') + + def _get_router_info_list_for_tenant(self, routers, tenant_id): + """Returns the list of router info objects on which to apply the fw.""" + router_info_list = [] + for router in routers: + # for routers without an interface - _get_routers returns + # the router - but this is not yet populated in router_info + if router['tenant_id'] != tenant_id: + continue + LOG.info(_LI("_get_router_info_list_for_tenant router %s"), router) + # This is done to Keep fwaas driver code unchanged + ri = RouterInfo(router) + router_info_list.append(ri) + return router_info_list + + def _is_interface_present_added_to_routers(self, appply_list): + for ri in appply_list: + router = ri.router + if router['svis']: + return True + return False + + def _invoke_driver_for_plugin_api(self, context, fw, func_name): + """Invoke driver method for plugin API and provide status back.""" + LOG.debug("%(func_name)s from agent for fw: %(fwid)s", + {'func_name': func_name, 'fwid': fw['id']}) + try: + routers = self._get_routers(context) + router_info_list = self._get_router_info_list_for_tenant( + routers, + fw['tenant_id']) + if not router_info_list and func_name != 'delete_firewall': + LOG.debug('No Routers on tenant: %s', fw['tenant_id']) + # fw was created before any routers were added, and if a + # delete is sent then we need to ack so that plugin can + # cleanup. + return + elif not self._is_interface_present_added_to_routers( + router_info_list) and func_name != 'delete_firewall': + LOG.debug('No Router interface') + return + LOG.debug("Apply fw on Router List: '%s'", + [ri.router['id'] for ri in router_info_list]) + # call into the driver + try: + self._fwaas_driver.__getattribute__(func_name)( + router_info_list, + fw) + if func_name != "delete_firewall": + self.endpoints[0].set_firewall_status(context, fw['id'], + const.ACTIVE) + except Exception as e: + LOG.error(_LE("Exception %s"), e) + LOG.error(_LE("Firewall Driver Error for %(func_name)s " + "for fw: %(fwid)s"), + {'func_name': func_name, 'fwid': fw['id']}) + raise e + + except Exception as e: + LOG.exception( + _LE("FWaaS RPC failure in %(func_name)s for fw: %(fwid)s"), + {'func_name': func_name, 'fwid': fw['id']}) + raise e + + return diff --git a/networking_brocade/vdx/non_ampp/ml2driver/fwaas_plugin.py b/networking_brocade/vdx/non_ampp/ml2driver/fwaas_plugin.py new file mode 100644 index 0000000..8ff2c42 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/fwaas_plugin.py @@ -0,0 +1,408 @@ +# Copyright 2016 Big Switch Networks, 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. + +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LW +from neutron.api.v2 import attributes as attr +from neutron.common import exceptions as n_exception +from neutron.common import rpc as n_rpc +from neutron.common import topics +from neutron import context as neutron_context +from neutron import manager +from neutron.plugins.common import constants as const +from oslo_config import cfg +from oslo_log import log as logging +import oslo_messaging + +from neutron_fwaas.db.firewall import firewall_db +from neutron_fwaas.db.firewall import firewall_router_insertion_db +from neutron_fwaas.extensions import firewall as fw_ext + + +LOG = logging.getLogger(__name__) + + +class FirewallCallbacks(object): + target = oslo_messaging.Target(version='1.0') + + def __init__(self, plugin): + super(FirewallCallbacks, self).__init__() + self.plugin = plugin + + def set_firewall_status(self, context, firewall_id, status, **kwargs): + """Agent uses this to set a firewall's status.""" + LOG.debug("Setting firewall %s to status: %s" % (firewall_id, status)) + # Sanitize status first + if status in (const.ACTIVE, const.DOWN, const.INACTIVE): + to_update = status + else: + to_update = const.ERROR + # ignore changing status if firewall expects to be deleted + # That case means that while some pending operation has been + # performed on the backend, neutron server received delete request + # and changed firewall status to PENDING_DELETE + updated = self.plugin.update_firewall_status( + context, firewall_id, to_update, not_in=(const.PENDING_DELETE,)) + if updated: + LOG.debug("firewall %s status set: %s" % (firewall_id, to_update)) + return updated and to_update != const.ERROR + + def firewall_deleted(self, context, firewall_id, **kwargs): + """Agent uses this to indicate firewall is deleted.""" + LOG.debug("firewall_deleted() called") + with context.session.begin(subtransactions=True): + fw_db = self.plugin._get_firewall(context, firewall_id) + # allow to delete firewalls in ERROR state + if fw_db.status in (const.PENDING_DELETE, const.ERROR): + self.plugin.delete_db_firewall_object(context, firewall_id) + return True + else: + LOG.warning(_LW('Firewall %(fw)s unexpectedly' + ' deleted by agent, ' + 'status was %(status)s'), + {'fw': firewall_id, 'status': fw_db.status}) + fw_db.update({"status": const.ERROR}) + return False + + def get_firewalls_for_tenant(self, context, **kwargs): + """Agent uses this to get all firewalls and rules for a tenant.""" + LOG.debug("get_firewalls_for_tenant() called") + fw_list = [] + for fw in self.plugin.get_firewalls(context): + fw_with_rules = self.plugin._make_firewall_dict_with_rules( + context, fw['id']) + if fw['status'] == const.PENDING_DELETE: + fw_with_rules['add-router-ids'] = [] + fw_with_rules['del-router-ids'] = ( + self.plugin.get_firewall_routers(context, fw['id'])) + else: + fw_with_rules['add-router-ids'] = ( + self.plugin.get_firewall_routers(context, fw['id'])) + fw_with_rules['del-router-ids'] = [] + fw_list.append(fw_with_rules) + return fw_list + + def get_firewalls_for_tenant_without_rules(self, context, **kwargs): + """Agent uses this to get all firewalls for a tenant.""" + LOG.debug("get_firewalls_for_tenant_without_rules() called") + fw_list = [fw for fw in self.plugin.get_firewalls(context)] + return fw_list + + def get_tenants_with_firewalls(self, context, **kwargs): + """Agent uses this to get all tenants that have firewalls.""" + LOG.debug("get_tenants_with_firewalls() called") + ctx = neutron_context.get_admin_context() + fw_list = self.plugin.get_firewalls(ctx) + fw_tenant_list = list(set(fw['tenant_id'] for fw in fw_list)) + return fw_tenant_list + + +class FirewallAgentApi(object): + + """Plugin side of plugin to agent RPC API.""" + + def __init__(self, topic, host): + self.host = host + target = oslo_messaging.Target(topic=topic, version='1.0') + self.client = n_rpc.get_client(target) + + def create_firewall(self, context, firewall): + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'create_firewall', firewall=firewall, + host=self.host) + + def update_firewall(self, context, firewall): + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'update_firewall', firewall=firewall, + host=self.host) + + def delete_firewall(self, context, firewall): + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'delete_firewall', firewall=firewall, + host=self.host) + + +class FirewallCountExceeded(n_exception.Conflict): + + """Reference implementation specific exception for firewall count. + + Only one firewall is supported per tenant. When a second + firewall is tried to be created, this exception will be raised. + """ + message = _("Exceeded allowed count of firewalls for tenant " + "%(tenant_id)s. Only one firewall is supported per tenant.") + + +class FirewallPlugin( + firewall_db.Firewall_db_mixin, + firewall_router_insertion_db.FirewallRouterInsertionDbMixin): + + """Implementation of the Neutron Firewall Service Plugin. + + This class manages the workflow of FWaaS request/response. + Most DB related works are implemented in class + firewall_db.Firewall_db_mixin. + """ + supported_extension_aliases = ["fwaas"] + path_prefix = fw_ext.FIREWALL_PREFIX + + def __init__(self): + """Do the initialization for the firewall service plugin here.""" + self.endpoints = [FirewallCallbacks(self)] + + self.conn = n_rpc.create_connection(new=True) + self.conn.create_consumer( + topics.FIREWALL_PLUGIN, self.endpoints, fanout=False) + self.conn.consume_in_threads() + + self.agent_rpc = FirewallAgentApi( + topics.L3_AGENT, + cfg.CONF.host + ) + firewall_db.subscribe() + + def _rpc_update_firewall(self, context, firewall_id): + status_update = {"firewall": {"status": const.PENDING_UPDATE}} + super(FirewallPlugin, self).update_firewall(context, firewall_id, + status_update) + fw_with_rules = self._make_firewall_dict_with_rules(context, + firewall_id) + # this is triggered on an update to fw rule or policy, no + # change in associated routers. + fw_with_rules['add-router-ids'] = self.get_firewall_routers( + context, firewall_id) + fw_with_rules['del-router-ids'] = [] + self.agent_rpc.update_firewall(context, fw_with_rules) + + def _rpc_update_firewall_policy(self, context, firewall_policy_id): + firewall_policy = self.get_firewall_policy(context, firewall_policy_id) + if firewall_policy: + for firewall_id in firewall_policy['firewall_list']: + self._rpc_update_firewall(context, firewall_id) + + def _ensure_update_firewall(self, context, firewall_id): + fwall = self.get_firewall(context, firewall_id) + if fwall['status'] in [const.PENDING_CREATE, + const.PENDING_UPDATE, + const.PENDING_DELETE]: + raise fw_ext.FirewallInPendingState(firewall_id=firewall_id, + pending_state=fwall['status']) + + def _ensure_update_firewall_policy(self, context, firewall_policy_id): + firewall_policy = self.get_firewall_policy(context, firewall_policy_id) + if firewall_policy and 'firewall_list' in firewall_policy: + for firewall_id in firewall_policy['firewall_list']: + self._ensure_update_firewall(context, firewall_id) + + def _ensure_update_firewall_rule(self, context, firewall_rule_id): + fw_rule = self.get_firewall_rule(context, firewall_rule_id) + if 'firewall_policy_id' in fw_rule and fw_rule['firewall_policy_id']: + self._ensure_update_firewall_policy(context, + fw_rule['firewall_policy_id']) + + def _get_routers_for_create_firewall(self, tenant_id, context, firewall): + + # pop router_id as this goes in the router association db + # and not firewall db + router_ids = firewall['firewall'].pop('router_ids', None) + if router_ids == attr.ATTR_NOT_SPECIFIED: + # old semantics router-ids keyword not specified pick up + # all routers on tenant. + l3_plugin = manager.NeutronManager.get_service_plugins().get( + const.L3_ROUTER_NAT) + ctx = neutron_context.get_admin_context() + routers = l3_plugin.get_routers(ctx) + router_ids = [ + router['id'] + for router in routers + if router['tenant_id'] == tenant_id] + # validation can still fail this if there is another fw + # which is associated with one of these routers. + self.validate_firewall_routers_not_in_use(context, router_ids) + return router_ids + else: + if not router_ids: + # This indicates that user specifies no routers. + return [] + else: + # some router(s) provided. + self.validate_firewall_routers_not_in_use(context, router_ids) + return router_ids + + def create_firewall(self, context, firewall): + LOG.debug("create_firewall() called") + tenant_id = self._get_tenant_id_for_create(context, + firewall['firewall']) + fw_count = self.get_firewalls_count(context, + filters={'tenant_id': [tenant_id]}) + if fw_count: + raise FirewallCountExceeded(tenant_id=tenant_id) + + fw_new_rtrs = self._get_routers_for_create_firewall( + tenant_id, context, firewall) + + if not fw_new_rtrs: + # no messaging to agent needed, and fw needs to go + # to INACTIVE(no associated rtrs) state. + status = const.INACTIVE + fw = super(FirewallPlugin, self).create_firewall( + context, firewall, status) + fw['router_ids'] = [] + return fw + else: + fw = super(FirewallPlugin, self).create_firewall( + context, firewall) + fw['router_ids'] = fw_new_rtrs + + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, fw['id'])) + + fw_with_rtrs = {'fw_id': fw['id'], + 'router_ids': fw_new_rtrs} + self.set_routers_for_firewall(context, fw_with_rtrs) + fw_with_rules['add-router-ids'] = fw_new_rtrs + fw_with_rules['del-router-ids'] = [] + + self.agent_rpc.create_firewall(context, fw_with_rules) + + return fw + + def update_firewall(self, context, id, firewall): + LOG.debug("update_firewall() called on firewall %s", id) + + self._ensure_update_firewall(context, id) + # pop router_id as this goes in the router association db + # and not firewall db + router_ids = firewall['firewall'].pop('router_ids', None) + fw_current_rtrs = self.get_firewall_routers(context, id) + if router_ids is not None: + if router_ids == []: + # This indicates that user is indicating no routers. + fw_new_rtrs = [] + else: + self.validate_firewall_routers_not_in_use( + context, router_ids, id) + fw_new_rtrs = router_ids + self.update_firewall_routers(context, {'fw_id': id, + 'router_ids': fw_new_rtrs}) + else: + # router-ids keyword not specified for update pick up + # existing routers. + fw_new_rtrs = self.get_firewall_routers(context, id) + + if not fw_new_rtrs and not fw_current_rtrs: + # no messaging to agent needed, and we need to continue + # in INACTIVE state + firewall['firewall']['status'] = const.INACTIVE + fw = super(FirewallPlugin, self).update_firewall( + context, id, firewall) + fw['router_ids'] = [] + return fw + else: + firewall['firewall']['status'] = const.PENDING_UPDATE + fw = super(FirewallPlugin, self).update_firewall( + context, id, firewall) + fw['router_ids'] = fw_new_rtrs + + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, fw['id'])) + + # determine rtrs to add fw to and del from + fw_with_rules['add-router-ids'] = fw_new_rtrs + fw_with_rules['del-router-ids'] = list( + set(fw_current_rtrs).difference(set(fw_new_rtrs))) + + # last-router drives agent to ack with status to set state to INACTIVE + fw_with_rules['last-router'] = not fw_new_rtrs + + LOG.debug("update_firewall %s: Add Routers: %s, Del Routers: %s", + fw['id'], + fw_with_rules['add-router-ids'], + fw_with_rules['del-router-ids']) + + self.agent_rpc.update_firewall(context, fw_with_rules) + + return fw + + def delete_db_firewall_object(self, context, id): + super(FirewallPlugin, self).delete_firewall(context, id) + + def delete_firewall(self, context, id): + LOG.debug("delete_firewall() called on firewall %s", id) + fw_with_rules = ( + self._make_firewall_dict_with_rules(context, id)) + fw_with_rules['del-router-ids'] = self.get_firewall_routers( + context, id) + fw_with_rules['add-router-ids'] = [] + if not fw_with_rules['del-router-ids']: + # no routers to delete on the agent side + self.delete_db_firewall_object(context, id) + else: + status = {"firewall": {"status": const.PENDING_DELETE}} + super(FirewallPlugin, self).update_firewall(context, id, status) + # Reflect state change in fw_with_rules + fw_with_rules['status'] = status['firewall']['status'] + self.agent_rpc.delete_firewall(context, fw_with_rules) + + def update_firewall_policy(self, context, id, firewall_policy): + LOG.debug("update_firewall_policy() called") + self._ensure_update_firewall_policy(context, id) + fwp = super(FirewallPlugin, + self).update_firewall_policy(context, id, firewall_policy) + self._rpc_update_firewall_policy(context, id) + return fwp + + def update_firewall_rule(self, context, id, firewall_rule): + LOG.debug("update_firewall_rule() called") + self._ensure_update_firewall_rule(context, id) + fwr = super(FirewallPlugin, + self).update_firewall_rule(context, id, firewall_rule) + firewall_policy_id = fwr['firewall_policy_id'] + if firewall_policy_id: + self._rpc_update_firewall_policy(context, firewall_policy_id) + return fwr + + def insert_rule(self, context, id, rule_info): + LOG.debug("insert_rule() called") + self._ensure_update_firewall_policy(context, id) + fwp = super(FirewallPlugin, + self).insert_rule(context, id, rule_info) + self._rpc_update_firewall_policy(context, id) + return fwp + + def remove_rule(self, context, id, rule_info): + LOG.debug("remove_rule() called") + self._ensure_update_firewall_policy(context, id) + fwp = super(FirewallPlugin, + self).remove_rule(context, id, rule_info) + self._rpc_update_firewall_policy(context, id) + return fwp + + def get_firewalls(self, context, filters=None, fields=None): + LOG.debug("fwaas get_firewalls() called") + fw_list = super(FirewallPlugin, self).get_firewalls( + context, filters, fields) + for fw in fw_list: + fw_current_rtrs = self.get_firewall_routers(context, fw['id']) + fw['router_ids'] = fw_current_rtrs + return fw_list + + def get_firewall(self, context, id, fields=None): + LOG.debug("fwaas get_firewall() called") + res = super(FirewallPlugin, self).get_firewall( + context, id, fields) + fw_current_rtrs = self.get_firewall_routers(context, id) + res['router_ids'] = fw_current_rtrs + return res diff --git a/networking_brocade/vdx/non_ampp/ml2driver/l3_router_plugin.py b/networking_brocade/vdx/non_ampp/ml2driver/l3_router_plugin.py new file mode 100644 index 0000000..a0cf168 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/l3_router_plugin.py @@ -0,0 +1,346 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2016 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. +# + + +"""Implentation of Brocade SVI service Plugin.""" +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade._i18n import _LW +from networking_brocade.vdx.db import models as brocade_db +from networking_brocade.vdx.non_ampp.ml2driver.nos import nosdriver as driver +from networking_brocade.vdx.non_ampp.ml2driver import utils +from neutron.api.v2 import attributes +from neutron.common import constants as l3_constants +from neutron.common import utils as neutron_utils +from neutron.db import models_v2 +from neutron.extensions import extraroute +from neutron import manager +from neutron.plugins.common import constants as plugin_constants +from neutron.services.l3_router import l3_router_plugin as router +from oslo_log import log as logging +from oslo_utils import excutils + +DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF +DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW +DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP + +LOG = logging.getLogger(__name__) + + +class BrocadeSVIPlugin(router.L3RouterPlugin): + + def __init__(self): + """Initialize Brocade Plugin. + Specify switch address and db configuration. + """ + super(BrocadeSVIPlugin, self).__init__() + self._driver = None + self._switch = None + self.brocade_init() + + def brocade_init(self): + """Brocade specific initialization.""" + LOG.debug("brocade init brocadeSVIPlugin") + self._switch = utils.get_brocade_credentials() + self._svi = utils.get_brocade_l3_config() + LOG.info(_LI("rbridge id %(rbridge id)s redundancy %(redundancy)s") % + {'rbridge id': self._svi['rbridge_ids'], + 'redundancy': self._svi['redundancy']}) + + self._driver = driver.NOSdriver(self._switch['address'], + self._switch['username'], + self._switch['password']) + if self._svi['redundancy']: + for rbridge_id in self._svi['rbridge_ids']: + LOG.info(_LI("rbridge id %s protocol vrrp enabled"), + rbridge_id) + self._driver.configure_protocol_vrrp(rbridge_id) + self._driver.close_session() + + def _update_ips_for_port(self, context, port): + LOG.info(_LI("_update_ips_for_port called()")) + port['fixed_ips'] is not attributes.ATTR_NOT_SPECIFIED + filter = {'network_id': [port['network_id']]} + subnets = self._core_plugin.get_subnets(context, filters=filter) + result = self._core_plugin.ipam._generate_ip(context, subnets) + LOG.info(_LI("_update_ips_for_port generated ip %s"), result) + + allocated = models_v2.IPAllocation(network_id=port['network_id'], + port_id=port['id'], + ip_address=result['ip_address'], + subnet_id=result['subnet_id']) + context.session.add(allocated) + return result + + def _invoke_nos_driver_api(self, func_name, router_id, + vlan_id=None, + gateway_ip_cidr=None, + context=None, + port=None, + added=None, + removed=None): + LOG.info(_LI("_invoke_nos_driver_api called()")) + self._switch + priority = 1 + if self._svi['redundancy'] and\ + (func_name == 'delete_svi' or + func_name == 'create_svi'): + vip, net_len = self.net_addr(gateway_ip_cidr) + LOG.info(_LI("_invoke_nos_driver_api vip %(vip)s" + " net_len %(net_len)s") % + {'vip': vip, 'net_len': net_len}) + + for rbridge_id in self._svi['rbridge_ids']: + if func_name == 'create_router' or\ + func_name == 'delete_router': + self._driver.__getattribute__(func_name)(rbridge_id, + str(router_id)) + elif func_name == 'create_svi': + if self._svi['redundancy']: + LOG.info(_LI("calling update_ips_for_port")) + res = self._update_ips_for_port(context, port) + gateway_ip_cidr = res['ip_address'] + '/' + str(net_len) + LOG.info(_LI("after _update_ips_for_port generated" + " gate_ip_cidr %s"), gateway_ip_cidr) + + LOG.info(_LI("invoke create svi")) + self._driver.__getattribute__(func_name)(rbridge_id, + vlan_id, + gateway_ip_cidr, + str(router_id)) + if self._svi['redundancy']: + vrrp_version = self._svi['vrrp_version'] + vrrp_group_id = self._svi['vrrp_group_id'] + vrrp_advt_interval =\ + self._svi['vrrp_advertisement_interval'] + try: + self._driver.configure_vrrp_on_svi(rbridge_id, + vlan_id, + vrrp_group_id, + vrrp_version, + vip, + vrrp_advt_interval, + priority) + priority += 1 + except Exception: + self._driver.delete_svi(rbridge_id, vlan_id, + gateway_ip_cidr, + str(router_id)) + raise "vrrp configuration failed on NOS" + + elif func_name == 'delete_svi': + self._driver.__getattribute__(func_name)(rbridge_id, + vlan_id, + gateway_ip_cidr, + str(router_id)) + elif func_name == 'update_router': + self._driver.__getattribute__(func_name)(rbridge_id, + str(router_id), + added, + removed) + + def create_router(self, context, router): + """creates a vrf on NOS device """ + LOG.debug("RouterMixin.create_router() called, " + "router=%s .", router) + r = router['router'] + self._get_tenant_id_for_create(context, r) + with context.session.begin(subtransactions=True): + new_router = super(BrocadeSVIPlugin, self).create_router(context, + router) + try: + # Router on VDX + self._invoke_nos_driver_api("create_router", new_router['id']) + except Exception as e: + LOG.error(_LE("Failed to create router Reason %s"), str(e)) + raise e + return new_router + + def _validate_routes_nexthop(self, cidrs, ips, routes, nexthop): + # lets skip to check connected routes + # lets keep it FR + if nexthop in ips: + raise extraroute.InvalidRoutes( + routes=routes, + reason=_('the nexthop is used by router')) + + def update_router(self, context, router_id, router): + """Update the router with static route""" + r = router['router'] + old_routes, routes_dict = self._get_extra_routes_dict_by_router_id( + context, router_id) + added, removed = neutron_utils.diff_list_of_dict(old_routes, + r['routes']) + try: + updated_router = super(BrocadeSVIPlugin, self).\ + update_router(context, router_id, router) + if 'routes' in r: + self._invoke_nos_driver_api('update_router', + router_id, + None, + None, + None, + None, + added, + removed) + except Exception as e: + LOG.error(_LE("Failed to modify route %s"), str(e)) + raise e + return updated_router + + def delete_router(self, context, router_id): + """delete a vrf on NOS device """ + LOG.debug("RouterMixin.delete_router() called, " + "router_id=%s .", router_id) + router = super(BrocadeSVIPlugin, self).get_router(context, router_id) + router['tenant_id'] + with context.session.begin(subtransactions=True): + super(BrocadeSVIPlugin, self).delete_router(context, router_id) + try: + self._invoke_nos_driver_api("delete_router", router['id']) + except Exception as e: + LOG.error(_LE("Failed to delete router Reason %s"), str(e)) + raise e + + def add_router_interface(self, context, router_id, interface_info): + """creates svi on NOS device and assigns ip addres to SVI""" + LOG.debug("BrocadeSVIPlugin.add_router_interface called: " + "router_id=%(router_id)s " + "interface_info=%(interface_info)r", + {'router_id': router_id, 'interface_info': interface_info}) + with context.session.begin(subtransactions=True): + info = super(BrocadeSVIPlugin, self).add_router_interface( + context, router_id, interface_info) + try: + port = self._core_plugin._get_port(context, info["port_id"]) + # shutting down neutron port to allow NOS to do Arp/Routing + # Propose to community to allow this to do more gracefully + port.update({"admin_state_up": False}) + interface_info = info + subnet = self._core_plugin._get_subnet(context, + interface_info["subnet_id"]) + cidr = subnet["cidr"] + net_addr, net_len = self.net_addr(cidr) + gateway_ip = subnet["gateway_ip"] + network_id = subnet['network_id'] + tenant_id = subnet['tenant_id'] + bnet = brocade_db.get_network(context, network_id) + vlan_id = bnet['vlan'] + gateway_ip_cidr = gateway_ip + '/' + str(net_len) + LOG.debug("Allocated cidr (%s) from the pool, network_id(%s)" + "bnet (%s) vlan (%d) ", gateway_ip_cidr, network_id, + bnet, int(vlan_id)) + port_filters = {'network_id': [network_id], + 'device_owner': [DEVICE_OWNER_ROUTER_INTF]} + port_count = self._core_plugin.get_ports_count(context, + port_filters) + LOG.info(_LI("BrocadeSVIPlugin.add_router_interface" + " ports_count %d"), port_count) + # port count is checked against 2 since the + # current port is already added to db + if port_count == 2: + # This subnet is already part of some router is not + # supported + # in this version of brocadesvi plugin + LOG.error(_LE("BrocadeSVIPlugin:adding redundent router" + "interface is not supported")) + raise Exception(_("BrocadeSVIPlugin:adding redundent" + "router interface is not supported")) + # res = self._update_ips_for_port(context, port) + # gateway_ip = res['ip_address'] + # gateway_ip_cidr = gateway_ip +'/'+str(net_len) + brocade_db.create_svi(context, router_id, tenant_id, str(vlan_id), + True, gateway_ip, str(net_len)) + self._invoke_nos_driver_api("create_svi", router_id, vlan_id, + gateway_ip_cidr, context, port) + self._update_firewall(context, vlan_id, tenant_id) + + except Exception: + LOG.error(_LE("Failed to create Brocade resources to add router " + "interface. info=%(info)s, router_id=%(router_id)s"), + {"info": info, "router_id": router_id}) + with excutils.save_and_reraise_exception(): + self._invoke_nos_driver_api("delete_svi", router_id, + vlan_id, gateway_ip_cidr) + with context.session.begin(subtransactions=True): + info = super(BrocadeSVIPlugin, self).\ + remove_router_interface(context, + router_id, + interface_info) + + return info + + def remove_router_interface(self, context, router_id, interface_info): + """Deletes svi from NOS device""" + LOG.debug("BrocadeSVIPlugin.remove_router_interface called: " + "router_id=%(router_id)s " + "interface_info=%(interface_info)r", + {'router_id': router_id, 'interface_info': interface_info}) + with context.session.begin(subtransactions=True): + info = super(BrocadeSVIPlugin, self).remove_router_interface( + context, router_id, interface_info) + + try: + subnet = self._core_plugin._get_subnet(context, info['subnet_id']) + cidr = subnet['cidr'] + net_addr, net_len = self.net_addr(cidr) + gateway_ip = subnet['gateway_ip'] + network_id = subnet['network_id'] + tenant_id = subnet['tenant_id'] + bnet = brocade_db.get_network(context, network_id) + vlan_id = bnet['vlan'] + gateway_ip_cidr = gateway_ip + '/' + str(net_len) + LOG.debug("remove_router_interface removed cidr (%s)" + "from the pool, network_id (%s) bnet (%s) vlan (%d) ", + gateway_ip_cidr, network_id, bnet, int(vlan_id)) + brocade_db.delete_svi(context, router_id, tenant_id, vlan_id, + gateway_ip, str(net_len)) + self._invoke_nos_driver_api("delete_svi", router_id, + vlan_id, gateway_ip_cidr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error(_LE("Failed to remove interface from brocade router" + "interface. info=%(info)s," + " router_id=%(router_id)s"), + {"info": info, "router_id": router_id}) + + def _update_firewall(self, context, svi, tenant_id): + """update newly added interface with firewall rules""" + fw_plugin = manager.NeutronManager.get_service_plugins().get( + plugin_constants.FIREWALL, None) + + if not fw_plugin: + LOG.info(_LI('No Firewall plugin registered!!')) + return + context.tenant_id = tenant_id + if hasattr(fw_plugin, 'handle_router_interface_add'): + fw_plugin.handle_router_interface_add(context, svi, tenant_id) + else: + LOG.warning(_LW("Brocade SVI Plugin is used but brocade firewall" + " plugin you may want to configure" + " brocade firewall plugin")) + + @staticmethod + def net_addr(addr): + """Get network address prefix and length from a given address.""" + if addr is None: + return (None, None) + nw_addr, nw_len = addr.split('/') + nw_len = int(nw_len) + return nw_addr, nw_len diff --git a/networking_brocade/vdx/non_ampp/ml2driver/mechanism_brocade.py b/networking_brocade/vdx/non_ampp/ml2driver/mechanism_brocade.py new file mode 100644 index 0000000..da4ba56 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/mechanism_brocade.py @@ -0,0 +1,493 @@ +# Copyright 2016 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. +# +# Shiv Haris (shivharis@hotmail.com) + + +"""Implentation of Brocade ML2 Mechanism driver for ML2 Plugin.""" + +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.bare_metal import util as baremetal_util +from networking_brocade.vdx.db import models as brocade_db +from networking_brocade.vdx.non_ampp.ml2driver.nos import nosdriver as driver +from networking_brocade.vdx.non_ampp.ml2driver import utils +from neutron.common import constants as n_const +from neutron import context as neutron_context +from neutron.extensions import portbindings +from neutron.plugins.common import constants as p_const +from neutron.plugins.ml2 import driver_api as api +import sys +try: + from oslo_log import log as logging +except ImportError: + from neutron.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) +MECHANISM_VERSION = 1.0 + + +class BrocadeMechanism(api.MechanismDriver): + + """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._device_dict = {} + self._bond_mappings = {} + self._lacp_ports = {} + self.initialize() + + def initialize(self): + """Initilize of variables needed by this class.""" + self.brocade_init() + + def brocade_init(self): + """Brocade specific initialization for this class.""" + utils.register_brocade_credentials() + self._switch = utils.get_brocade_credentials() + self._fqdn_supported = utils.is_fqdn_supported() + self.initialize_vcs = utils.get_vcs_initialize() + self._physical_networks = utils.get_physical_networks() + self._driver = driver.NOSdriver(self._switch['address'], + self._switch['username'], + self._switch['password']) + try: + self._device_dict, self._bond_mappings, self._mtu,\ + self._native_vlans = utils._parse_connection_info() + except Exception as e: + LOG.error(_LE("%s"), e) + sys.exit(0) + + if self.initialize_vcs: + self.configure_vcs() + self._driver.close_session() + + def configure_vcs(self): + # configure vcs interfaces based on topology + if not utils._is_valid_interface(self._device_dict, + self._switch, self._driver): + sys.exit(0) + + LOG.debug("device dictionary %s", self._device_dict) + + try: + if utils._is_lacp_enabled(): + LOG.debug("LACP enabled") + (self._device_dict, self._lacp_ports) =\ + utils._aggregate_nics_to_support_lacp(self._device_dict, + self._bond_mappings) + self._driver.configure_l2_and_trunk_mode_for_interface( + self._device_dict, self._lacp_ports, + self._mtu, self._native_vlans) + except Exception: + LOG.exception( + _LE("Brocade Mechanism: failed to put" + " interface l2 or tr mode")) + raise Exception( + _("Brocade Mechanism: failed to put interface l2 or tr mode")) + + def is_flat_network(self, segment): + if not segment or segment['network_type'] == p_const.TYPE_FLAT: + LOG.info(_LI("Flat network nothing to be done")) + return True + return False + + def create_network_precommit(self, mech_context): + """Create Network in the mechanism specific database table.""" + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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 network_type not in [p_const.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( + _LE("Brocade Mechanism: failed to create network in db")) + raise Exception( + _("Brocade Mechanism: create_network_precommit failed")) + + def create_network_postcommit(self, mech_context): + """Create Network as a portprofile on the switch.""" + + LOG.debug("create_network_postcommit: called") + + if self.is_flat_network(mech_context.network_segments[0]): + return + + 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['network_type'] + network['tenant_id'] + vlan_id = network['vlan'] + segments = mech_context.network_segments + # currently supports only one segment per network + segment = segments[0] + physical_network = segment['physical_network'] + + try: + self._driver.create_network(self._device_dict, + physical_network, + vlan_id) + except Exception: + LOG.exception(_LE("Brocade NOS driver: failed in create network")) + brocade_db.delete_network(context, network_id) + raise Exception( + _("Brocade Mechanism: create_network_postcommmit failed")) + + def delete_network_precommit(self, mech_context): + """Delete Network from the plugin specific database table.""" + + LOG.debug("delete_network_precommit: called") + if self.is_flat_network(mech_context.network_segments[0]): + return + + network = mech_context.current + network_id = network['id'] + network['tenant_id'] + + context = mech_context._plugin_context + + try: + brocade_db.delete_network(context, network_id) + except Exception: + LOG.exception( + _LE("Brocade Mechanism: failed to delete network in db")) + raise Exception( + _("Brocade Mechanism: delete_network_precommit failed")) + + def delete_network_postcommit(self, mech_context): + """Delete network which translates to removng portprofile + from the switch. + """ + LOG.debug("delete_network_postcommit: called") + if self.is_flat_network(mech_context.network_segments[0]): + return + + network = mech_context.current + network['id'] + vlan_id = network['provider:segmentation_id'] + network['tenant_id'] + try: + self._driver.delete_network(vlan_id) + except Exception: + LOG.exception(_LE("Brocade NOS driver: failed to delete network")) + raise Exception( + _("Brocade switch exception, " + "delete_network_postcommit failed")) + + def update_network_precommit(self, mech_context): + """Noop now, it is left here for future.""" + + def update_network_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + + def create_port_precommit(self, mech_context): + """Create logical port on the switch (db update).""" + LOG.debug("create_port_precommit: called") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + port = mech_context.current + if not self._is_compute_or_dhcp_port(port, mech_context): + return + if baremetal_util.is_baremetal_deploy(port): + LOG.debug("create_port_precommit: baremetal deploy") + return + context = neutron_context.get_admin_context() + self._create_brocade_port( + context, port, mech_context.top_bound_segment) + + def create_port_postcommit(self, mech_context): + """Associate the assigned MAC address to the portprofile.""" + LOG.debug("create_port_postcommit(self: called") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + port = mech_context.current + if not self._is_compute_or_dhcp_port(port, mech_context): + return + context = mech_context._plugin_context + self._create_nos_port(context, port, mech_context.top_bound_segment) + + def delete_port_precommit(self, mech_context): + """Delete logical port on the switch (db update).""" + + LOG.debug("delete_port_precommit: called") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + port = mech_context.current + if not self._is_compute_or_dhcp_port(port, mech_context): + return + + context = mech_context._plugin_context + self._delete_brocade_port(context, port) + + def delete_port_postcommit(self, mech_context): + """Dissociate MAC address from the portprofile.""" + LOG.debug("delete_port_postcommit(self: called") + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + port = mech_context.current + if not self._is_compute_or_dhcp_port(port, mech_context): + return + + context = mech_context._plugin_context + self._delete_nos_port(context, port, mech_context.top_bound_segment) + + def update_port_precommit(self, mech_context): + """updates brocade db if vm is migrating""" + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + context = mech_context._plugin_context + port = mech_context.current + LOG.debug("update_port_precommit(self: called") + if not self._is_compute_or_dhcp_port(port, mech_context): + return + + if self._is_vm_migration(mech_context): + # PortContext.current['binding:host_id']: current (new) value + port = mech_context.original + LOG.debug("update_port_precommit: VM is migrating to" + "new host %s(case 1) port['status'] %s", + port[portbindings.HOST_ID], port['status']) + self._delete_brocade_port(context, port) + else: + # PortContext.current['binding:host_id']: previous value + if mech_context.top_bound_segment and\ + port['status'] == n_const.PORT_STATUS_BUILD: + LOG.debug("update_port_pretcommit: VM is migrating to" + "new host %s(case 2)", port[portbindings.HOST_ID]) + self._create_brocade_port(context, port, + mech_context.top_bound_segment) + + def update_port_postcommit(self, mech_context): + """updates brocade nos if vm is migrating""" + if self.is_flat_network(mech_context.network.network_segments[0]): + return + + port = mech_context.current + context = mech_context._plugin_context + LOG.debug("update_port_postcommit: called") + if not self._is_compute_or_dhcp_port(port, mech_context): + return + + if self._is_vm_migration(mech_context): + # add new entry to switch + # PortContext.current['binding:host_id']: current (new) value + port = mech_context.original + LOG.debug("update_port_precommit: VM is migrating to" + "new host %s(case 1) port['status'] %s", + port[portbindings.HOST_ID], port['status']) + self._delete_nos_port(context, port, + mech_context.original_bound_segment) + else: + # remove previouse port binings + # PortContext.current['binding:host_id']: previous value + if mech_context.top_bound_segment and\ + port['status'] == n_const.PORT_STATUS_BUILD: + LOG.debug("update_port_postcommit: VM is migrating to" + "new host %s(case 2)", port[portbindings.HOST_ID]) + self._create_nos_port(context, port, + mech_context.top_bound_segment) + + 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") + + def _is_vm_migration(self, context): + LOG.debug("_is_vm_migration called") + return (context.current.get(portbindings.HOST_ID) != + context.original.get(portbindings.HOST_ID)) + + def _is_compute_or_dhcp_port(self, port, context): + if (("compute" not in port['device_owner']) and + ("dhcp" not in port['device_owner'])): + # Not a compute port or dhcp , return + return False + #if not self._is_profile_bound_to_port(port, context): + # it is baremetal port + # return False + return True + + def _is_profile_bound_to_port(self, port, context): + profile = context.current.get(portbindings.PROFILE, {}) + if not profile: + LOG.debug("Missing profile in port binding") + return False + return True + + def _is_dhcp_port(self, port): + if("dhcp" in port['device_owner']): + # dhcp port, return + return True + return False + + def _get_vlanid(self, segment): + if (segment and segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN): + return segment.get(api.SEGMENTATION_ID) + + def _get_physical_interface(self, segment): + if (segment and segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN): + return segment.get(api.PHYSICAL_NETWORK) + + def _get_hostname(self, port): + host = port[portbindings.HOST_ID] + LOG.debug("_get_hostname host %s", host) + return host if self._fqdn_supported else host.split('.')[0] + + def _get_port_info(self, port, segment): + "get vlan id and physical networkkfrom bound segment" + if port and segment: + vlan_id = self._get_vlanid(segment) + hostname = self._get_hostname(port) + physical_interface = self._get_physical_interface(segment) + LOG.debug("_get_port_info: hostname %s, vlan_id %s," + " physical_interface %s", hostname, str(vlan_id), + physical_interface) + return hostname, vlan_id, physical_interface + return None, None, None + + def _create_brocade_port(self, context, port, segment): + port_id = port['id'] + network_id = port['network_id'] + tenant_id = port['tenant_id'] + admin_state_up = port['admin_state_up'] + hostname, vlan_id, physical_network = self._get_port_info( + port, segment) + try: + brocade_db.create_port(context, port_id, network_id, + physical_network, vlan_id, tenant_id, + admin_state_up, hostname) + except Exception: + LOG.exception(_LE("Brocade Mechanism: " + "failed to create port in db")) + raise Exception( + _("Brocade Mechanism: create_port_precommit failed")) + + def _create_nos_port(self, context, port, segment): + hostname, vlan_id, physical_network = self._get_port_info( + port, segment) + if not hostname or not vlan_id: + LOG.info(_LI("hostname or vlan id is empty")) + return + for (speed, name) in self._device_dict[(hostname, physical_network)]: + LOG.debug("_create_nos_port:port %s %s vlan %s", + speed, name, str(vlan_id)) + try: + if not brocade_db.is_vm_exists_on_host(context, + hostname, + physical_network, + vlan_id): + self._driver.add_or_remove_vlan_from_interface( + "add", speed, name, vlan_id) + else: + LOG.debug("_create_nos_port:port is already trunked") + except Exception: + self._delete_brocade_port(context, port) + LOG.exception(_LE("Brocade NOS driver:failed to trunk vlan")) + raise Exception(_("Brocade switch exception:" + " create_port_postcommit failed")) + + def _delete_brocade_port(self, context, port): + try: + port_id = port['id'] + brocade_db.delete_port(context, port_id) + except Exception: + LOG.exception(_LE("Brocade Mechanism:" + " failed to delete port in db")) + raise Exception( + _("Brocade Mechanism: delete_port_precommit failed")) + + def _delete_nos_port(self, context, port, segment): + + hostname, vlan_id, physical_network =\ + self._get_port_info(port, segment) + if not hostname or not vlan_id: + LOG.info(_LI("hostname or vlan id is empty")) + return + for (speed, name) in self._device_dict[(hostname, physical_network)]: + try: + if brocade_db.is_last_vm_on_host(context, + hostname, + physical_network, vlan_id)\ + and not self._is_dhcp_port(port): + + self._driver.add_or_remove_vlan_from_interface("remove", + speed, + name, + vlan_id) + else: + LOG.info(_LI("more vm exist for network on host hence vlan" + " is not removed from port")) + except Exception: + LOG.exception( + _LE("Brocade NOS driver: failed to remove vlan from port")) + raise Exception( + _("Brocade switch exception: delete_port_postcommit" + "failed")) diff --git a/networking_brocade/vdx/non_ampp/ml2driver/nos/__init__.py b/networking_brocade/vdx/non_ampp/ml2driver/nos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/networking_brocade/vdx/non_ampp/ml2driver/nos/nctemplates.py b/networking_brocade/vdx/non_ampp/ml2driver/nos/nctemplates.py new file mode 100644 index 0000000..12fe087 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/nos/nctemplates.py @@ -0,0 +1,825 @@ +# Copyright (c) 2016 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 +""" + +# Get NOS Version +SHOW_FIRMWARE_VERSION = ( + "show-firmware-version xmlns:nc=" + "'urn:brocade.com:mgmt:brocade-firmware-ext'" +) + +NOS_VERSION = "./*/{urn:brocade.com:mgmt:brocade-firmware-ext}os-version" + +# +# L2 Forwarding Life-cycle Management Configuration Commands +# + +CREATE_INTERFACE = """ + + + + {name} + + + +""" + +PORT_CHANNEL_SPEED = """ + + + + {name} + {po_speed} + + + +""" +PORT_CHANNEL_LB_MODE = """ + + + + {name} + {po_lb_mode} + + + +""" + +ACTIVATE_INTERFACE = """ + + + <{speed}> + {name} + + + + +""" +REMOVE_CHANNEL_GROUP = """ + + + <{speed}> + {name} + + + + + +""" +CONFIGURE_CHANNEL_GROUP = """ + + + <{speed}> + {name} + + {port} + {po_mode} + {po_type} + + + + +""" +CONFIGURE_INTERFACE_SWITCHPORT_V1 = """ + + + <{speed}> + {name} + + + + + + +""" +REMOVE_INTERFACE_SWITCHPORT_V1 = """ + + + <{speed}> + {name} + + + + + + +""" + +REMOVE_PORT_PROFILE_PORT = """ + + + <{speed}> + {name} + + + + + +""" + +CONFIGURE_MTU_ON_INTERFACE = """ + + + <{speed}> + {name} + {mtu} + + + +""" + +CONFIGURE_INTERFACE_SWITCHPORT_V2 = """ + + + <{speed}> + {name} + + + + + + +""" + +REMOVE_INTERFACE_SWITCHPORT_V2 = """ + + + <{speed}> + {name} + + + + + + +""" + +CONFIGURE_INTERFACE_SWITCHPORT_TRUNK = """ + + + <{speed}> + {name} + + + trunk + + + + + +""" + +ADD_OR_REMOVE_VLAN_TO_INTERFACE = """ + + + <{speed}> + {name} + + + + + <{action}>{vlan_id} + + + + + + + +""" + +ALLOW_UNTAG_TRAF_ON_INTERFACE = """ + + + <{speed}> + {name} + + + + + + + + + + +""" + + +ADD_NATIVE_VLAN_TO_INTERFACE = """ + + + <{speed}> + {name} + + + + {vlan_id} + + + + + + +""" + +REMOVE_NATIVE_VLAN_FROM_INTERFACE = """ + + + <{speed}> + {name} + + + + + + + + + + +""" +# Create VLAN (vlan_id) +CREATE_VLAN_INTERFACE = """ + + + + + {vlan_id} + + + + +""" + +# Delete VLAN (vlan_id) +DELETE_VLAN_INTERFACE = """ + + + + + {vlan_id} + + + + +""" + +# +# L3 Life-cycle Management Configuration Commands +# + +# configure SVI (rbridge_id,vlan_id) +CONFIGURE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + +""" +# Create IP static routes (rbridge_id,vrf_name,destination_ip,next_hop) +CONFIGURE_VRF_IP_STATIC_ROUTE = """ + + + {rbridge_id} + + {vrf_name} + + + + + + + {destination_ip} + {next_hop} + + + + + + + + + +""" +# Delete IP static routes (rbridge_id,vrf_name,destination_ip,next_hop) +DELETE_VRF_IP_STATIC_ROUTE = """ + + + {rbridge_id} + + {vrf_name} + + + + + + + {destination_ip} + {next_hop} + + + + + + + + + +""" +# Create IP static routes (rbridge_id,destination_ip,next_hop) +CONFIGURE_IP_STATIC_ROUTE = """ + + + {rbridge_id} + + + + + {destination_ip} + {next_hop} + + + + + + +""" +# Delete IP static routes (rbridge_id,destination_ip,next_hop) +DELETE_IP_STATIC_ROUTE = """ + + + {rbridge_id} + + + + + {destination_ip} + {next_hop} + + + + + + +""" +# Create SVI and assign ippaddres (rbridge_id,vlan_id,ip_address) +CONFIGURE_SVI_WITH_IP_ADDRESS = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{ip_address}
+
+
+
+
+
+
+
+""" + +# delete SVI (rbridge_id,vlan_id) +DELETE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + +""" + +# Activate SVI (rbridge_id,vlan_id) +ACTIVATE_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + + + + +""" + +# Remove ipaddress from SVI (rbridge_id,vlan_id) +DECONFIGURE_IP_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + +
+
{gw_ip}
+
+
+
+
+
+
+
+""" + +# create vrf (rbridge_id,vrf_name) +CREATE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + + +# delete vrf (rbridge_id,vrf_name) +DELETE_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + +""" + +# configure route distinguisher for vrf (rbridge_id,vrf_name, rd) +CONFIGURE_RD_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + {rd} + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF_V1 = """ + + + {rbridge_id} + + {vrf_name} + + + 1200 + + + + + +""" + +# configure address-family for vrf (rbridge_id,vrf_name) +ADD_ADDRESS_FAMILY_FOR_VRF = """ + + + {rbridge_id} + + {vrf_name} + + + + + + + + +""" + +# Bind vrf to SVI (rbridge_id,vlan_idi, vrf) +ADD_VRF_TO_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + +# unbind vrf from SVI (rbridge_id,vlan_idi, vrf) +DELETE_VRF_FROM_SVI = """ + + + {rbridge_id} + + + {vlan_id} + + {vrf_name} + + + + + +""" + +# Acl Policy Life cycle Management +REMOVE_ACL_POLICY = """ + + + + + + {acl_name} + + + + + +""" +IP_ACL_RULE_BULKING_START = """ + + + + + + {name} + +""" +IP_ACL_RULE_BULKING_END = """ + + + + + + +""" + + +IP_ACL_RULE = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" +SVI_IP_ACL = """ + + + {rbridge_id} + + + {vlan_id} + + + + {name} + {direction} + + + + + + + +""" + +REMOVE_SVI_IP_ACL = """ + + + {rbridge_id} + + + {vlan_id} + + + + {name} + {direction} + + + + + + + +""" + +# L3 HA management commands +ENABLE_VRRP = """ + + + {rbridge_id} + + + + + + + +""" + +CONFIGURE_VRRP_GROUP = """ + + + {rbridge_id} + + + {vlan_id} + + {vrid} + {version} + + + + + +""" + + +CONFIGURE_VRRP_VIP = """ + + + {rbridge_id} + + + {vlan_id} + + {vrid} + {version} + + {vip} + + + + + + +""" + +CONFIGURE_VRRP_PRIORITY = """ + + + {rbridge_id} + + + {vlan_id} + + {vrid} + {version} + {priority} + + + + + +""" + +CONFIGURE_VRRP_ADVERTISEMENT_INTERVEL = """ + + + {rbridge_id} + + + {vlan_id} + + {vrid} + {version} + {advt_int} + + + + + +""" + + +# Constants +# + +# Constants +# +# ip acl naming convention +ROUTER_OBJ_PREFIX = 'openstack-acl-' + +# vrf naming convention +OS_VRF_NAME = "openstack-vrf-{id}" + +BAD_ELE = "bad-element" +IP_ACL_APPLIED = "SSM_DCM_ERR_IP_ACL_APPLIED" + +SEQ_ID_EXISTS = "/ip-acl/ip/access-list/extended[name='{name}']/hide-ip-acl-"\ + "ext/seq [seq-id='{id}']" + +IP_ACL_NAME_XPATH_FILTER = "/ip-acl/ip/access-list/extended[name='{name}']/"\ + "name" + +ACL_ON_SVIS_XPATH_FILTER = "/rbridge-id[rbridge-id='{rbridge_id}']/interface/"\ + "ve/ip-acl-interface/ip/access-group/ip-access-list" + +ACL_ON_SVI_XPATH_FILTER = "/rbridge-id[rbridge-id='{rbridge_id}']/interface/"\ + "ve[name='{svi}']/ip-acl-interface/ip/access-group/"\ + "ip-access-list" + +SVI_STATUS_XPATH_FILTER = "/rbridge-id[rbridge-id='{rbridge_id}']/interface/"\ + "ve[name='{name}']/shutdown" + +INTERFACE_XPATH_FILTER = "/interface/{speed}[name='{name}']/name" + +SVI_EXISTS_XPATH_FILTER = "/rbridge-id[rbridge-id='{rbridge_id}']/interface/"\ + "ve[name='{name}']/name" + +INTERFACE_STATUS_XPATH_FILTER = "/interface/{speed}[name='{name}']/shutdown" + +INTERFACE_PP_STATUS_XPATH_FILTER = "/interface/{speed}[name='{name}']/*"\ + "[local-name()='port-profile-port']" +INTERFACE_CG_STATUS_XPATH_FILTER = "/interface/{speed}[name='{name}']/*"\ + "[local-name()='channnel-group']" diff --git a/networking_brocade/vdx/non_ampp/ml2driver/nos/nosdriver.py b/networking_brocade/vdx/non_ampp/ml2driver/nos/nosdriver.py new file mode 100644 index 0000000..2d307e8 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/nos/nosdriver.py @@ -0,0 +1,1013 @@ +# Copyright 2016 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 ncclient.operations.errors import TimeoutExpiredError +from ncclient.transport.errors import TransportError +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade._i18n import _LW +from networking_brocade.vdx.non_ampp.ml2driver import utils +from networking_brocade.vdx.non_ampp.ml2driver.nos import( + nctemplates as template) +from neutron.common import exceptions +from oslo_log import log as logging +from oslo_utils import excutils +import six +import sys +import time + +LOG = logging.getLogger(__name__) +SSH_PORT = 22 +RETRYABLE_ERRORS = ["NODE_IS_NOT_READY", + "CLUSTER_FORMATION_IS_IN_PROGRESS", + "NODE_IS_ZEROIZED", + "WAVE_FRAMEWORK_STATE_CLUSTER_FORMATION" + ] + +_RETRIES, _NDELAY, _NBACKOFF = utils.get_retry_args() + + +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 + +# 5 retries is equal to 8 mins + + +def retry(ExceptionToCheck, tries=_RETRIES, delay=_NDELAY, backoff=_NBACKOFF): + """Retry decorator + """ + def deco_retry(f): + def f_retry(*args, **kwargs): + mtries, mdelay = tries, delay + while mtries > 0: + try: + return f(*args, **kwargs) + except ExceptionToCheck as e: + LOG.warning(_LW("Retrying in %d seconds..."), mdelay) + time.sleep(mdelay) + mtries -= 1 + mdelay *= backoff + lastException = e + raise lastException + return f_retry # true decorator + return deco_retry + + +class RetryableException(exceptions.NeutronException): + message = _("Transient errors Try again after some time." + " Reason: %(exc)s.") + + +class NOSdriver(object): + + """NOS NETCONF interface driver for Neutron network. + Handles life-cycle management of Neutron network + """ + + def __init__(self, host, username, password): + self.mgr = None + self.host = host + self.username = username + self.password = password + self.osversion = self.get_nos_version().split('.', 2) + + def _set_default_timeout_ncclient(self): + mgr = self.connect(self.host, self.username, self.password) + mgr.timeout = 30 + + @retry(RetryableException) + def _edit_config(self, target, config, timeout=30): + """Modify switch config for a target config type.""" + try: + mgr = self.connect(self.host, self.username, self.password) + if timeout != 30: + mgr.timeout = timeout + mgr.edit_config(target=target, config=config) + except TransportError as e: + self.close_session() + LOG.warning(_LW("_edit_config()TransportErrorFailed" + "for Reason %(exc)s"), {'exc': e}) + raise RetryableException(exc=e) + except TimeoutExpiredError as e: + LOG.warning(_LW("_edit_config(TimeoutExpiredError)" + "for Reason %(exc)s"), {'exc': e}) + raise RetryableException(exc=e) + except Exception as e: + LOG.warning(_LW("_edit_config(CLUSTER ERRORS)" + "for Reason %(exc)s"), {'exc': e}) + for exc_str in RETRYABLE_ERRORS: + if exc_str in str(e): + raise RetryableException(exc=e) + raise e + finally: + if timeout != 30: + self._set_default_timeout_ncclient() + + @retry(RetryableException) + def _get_config(self, source, filterstr): + """get switch config for a source config type.""" + try: + mgr = self.connect(self.host, self.username, self.password) + response = mgr.get_config(source=source, + filter=('xpath', filterstr)).data_xml + return response + except TransportError as e: + LOG.warning(_LW("_edit_config()TransportErrorFailed" + "for Reason %s"), unicode(str(e))) + self.close_session() + raise RetryableException(exc=e) + except TimeoutExpiredError as e: + LOG.warning(_LW("_edit_config(TimeoutExpiredError)" + "for Reason %s"), unicode(str(e))) + raise RetryableException(exc=e) + except Exception as e: + LOG.warning(_LW("_edit_config(CLUSTER ERRORS)" + "for Reason %s"), unicode(str(e))) + for exc_str in RETRYABLE_ERRORS: + if exc_str in str(e): + raise RetryableException(exc=e) + raise e + + 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: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Connect failed to switch")) + + 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 and self.mgr.connected: + self.mgr.close_session() + self.mgr = None + + def get_nos_version(self): + """Show version of NOS.""" + try: + mgr = self.connect(self.host, self.username, self.password) + return self.nos_version_request(mgr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + self.close_session() + + def create_network(self, topology, + physical_network, net_id): + """Creates a new virtual network.""" + try: + self.create_vlan_interface(net_id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def delete_network(self, net_id): + """Deletes a virtual network.""" + try: + self.delete_vlan_interface(net_id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def configure_l2_and_trunk_mode_for_interface(self, devices, lacp, + mtu, native_vlans): + """configure interface in switchport and trunk mode.""" + for key in lacp.keys(): + lacp_args = utils.get_lacp_args() + create_interface = template.CREATE_INTERFACE.format(name=key) + configure_lb_mode = template.PORT_CHANNEL_LB_MODE.format( + name=key, + po_lb_mode=lacp_args['po_lb_mode']) + configure_po_speed = template.PORT_CHANNEL_SPEED.format( + name=key, + po_speed=lacp_args['po_speed']) + if not self.is_interface_id_exists('port-channel', key): + self._edit_config('running', create_interface) + self._edit_config('running', configure_lb_mode) + self._edit_config('running', configure_po_speed) + + for (speed, interface_name) in lacp[key]: + self.remove_l2_mode_for_interface(speed, interface_name, + lacp_args['remove_ch_grp']) + + for (speed, interface_name) in lacp[key]: + confstr_channel_group = template.\ + CONFIGURE_CHANNEL_GROUP.format( + speed=speed, name=interface_name, + port=key, po_mode=lacp_args[ + 'po_mode'], + po_type=lacp_args['po_type']) + self._edit_config('running', confstr_channel_group) + self.activate_interface(speed, interface_name) + + for key in devices.keys(): + for (interface_speed, interface_name) in devices[key]: + + confstr_trunk = template.CONFIGURE_INTERFACE_SWITCHPORT_TRUNK.\ + format(speed=interface_speed, + name=interface_name) + try: + if not self.is_interface_id_exists(interface_speed, + interface_name): + LOG.error(_LE("topology incorrect incorrect" + " VDX interface" + "%(interface_speed)s," + "%(interface_name)s") + % {'interface_speed': interface_speed, + 'interface_name': interface_name}) + sys.exit(0) + self.configure_l2_mode_for_interface(interface_speed, + interface_name) + self.configure_interface_in_trunk_mode(confstr_trunk) + self.activate_interface(interface_speed, interface_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE( + "VDX interfaces may not" + " be in proper " + "mode configure switchport mode")) + + for (speed, name), mtu in six.iteritems(mtu): + self.configure_mtu_on_interface(speed, name, mtu) + + for (speed, name), vlan_id in six.iteritems(native_vlans): + self.create_vlan_interface(vlan_id) + self.configure_native_vlan_on_interface(speed, name, vlan_id) + + def configure_interface_in_trunk_mode(self, confstr_trunk): + self._edit_config('running', confstr_trunk) + + def activate_interface(self, interface_speed, interface_name): + """Activate physical interface """ + if not self.is_interface_shutdown(interface_speed, interface_name): + return + confstr_activate = template.ACTIVATE_INTERFACE.format( + speed=interface_speed, name=interface_name) + try: + self._edit_config('running', confstr_activate) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("interface already in active state")) + ctxt.reraise = False + + def remove_l2_mode_for_interface(self, interface_speed, + interface_name, remove_ch_grp=False): + """Configures given interface in L2 mode""" + version = self.osversion + if int(version[0]) >= 5 or (int(version[0]) >= 4 and + int(version[1]) >= 1): + confstr = template.REMOVE_INTERFACE_SWITCHPORT_V1.format( + speed=interface_speed, name=interface_name) + else: + confstr = template.REMOVE_INTERFACE_SWITCHPORT_V2.format( + speed=interface_speed, name=interface_name) + confstr_rm_cg = template.REMOVE_CHANNEL_GROUP.format( + speed=interface_speed, name=interface_name) + try: + try: + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + ctxt.reraise = False + if remove_ch_grp: + self._edit_config('running', confstr_rm_cg) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + ctxt.reraise = False + + def configure_l2_mode_for_interface(self, interface_speed, + interface_name): + """Configures given interface in L2 mode""" + if self.is_interface_in_port_profile_mode(interface_speed, + interface_name): + try: + self.set_interface_to_accept_l2_mode(interface_speed, + interface_name) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("interface already in active state")) + ctxt.reraise = False + + try: + if (interface_speed != 'port-channel'): + confstr = template.REMOVE_CHANNEL_GROUP.format( + speed=interface_speed, name=interface_name) + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("exception cg removing")) + ctxt.reraise = False + + try: + version = self.osversion + if int(version[0]) >= 5 or (int(version[0]) >= 4 and + int(version[1]) >= 1): + confstr = template.CONFIGURE_INTERFACE_SWITCHPORT_V1.format( + speed=interface_speed, name=interface_name) + else: + confstr = template.CONFIGURE_INTERFACE_SWITCHPORT_V2.format( + speed=interface_speed, name=interface_name) + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW( + "interface not accepting switching please check" + "innterface status")) + + def add_or_remove_vlan_from_interface(self, action, interface_speed, + interface_name, vlan_id): + """add or remove vlan on interface""" + + confstr = template.ADD_OR_REMOVE_VLAN_TO_INTERFACE.format( + speed=interface_speed, name=interface_name, + action=action, vlan_id=vlan_id) + try: + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def create_svi(self, rbridge_id, vlan_id, + ip_address, router_id): + """create svi on configured rbridge-id""" + try: + self.configure_svi(rbridge_id, vlan_id) + self.bind_vrf_to_svi(rbridge_id, vlan_id, router_id) + self.configure_svi_with_ip_address(rbridge_id, vlan_id, ip_address) + self.activate_svi(rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error: %s"), ex) + self.delete_svi(rbridge_id, vlan_id, ip_address, router_id) + + def delete_svi(self, rbridge_id, vlan_id, + gw_ip, router_id): + """delete svi from configured rbridge-id""" + try: + if self.is_svi_exists(rbridge_id, vlan_id): + self.remove_svi(rbridge_id, vlan_id) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error: %s"), ex) + + def create_router(self, rbridge_id, router_id): + """create vrf NOS""" + if not utils.is_vrf_required(): + LOG.warning(_LW("not requested to created vrf there will" + "no L5 traffic isolation and no overlapping IP" + "supported")) + return + vrf_name = template.OS_VRF_NAME.format(id=router_id) + vrf_name = vrf_name[:32] + # This is done because on 4.0.0 rd doesnt accept + # alpha character nor hyphen + rd = "".join(i for i in router_id if i in "0123456789") + rd = rd[:4] + ":" + rd[:4] + try: + self.create_vrf(rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + try: + self.configure_rd_for_vrf(rbridge_id, vrf_name, rd) + self.configure_address_family_for_vrf(rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def delete_router(self, rbridge_id, router_id): + """create vrf NOS""" + if not utils.is_vrf_required(): + return + vrf_name = template.OS_VRF_NAME.format(id=router_id) + vrf_name = vrf_name[:32] + try: + self.delete_vrf(rbridge_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def update_router(self, rbridge_id, router_id, added, removed): + """update router""" + LOG.info(_LI("Inside update_router before sending to switch")) + if added is None: + LOG.info(_LI("Added is None")) + + if removed is None: + LOG.info(_LI("Removed is None")) + + if not utils.is_vrf_required(): + """ Configure the static route at the rbridge mode""" + try: + if added is not None: + LOG.info(_LI("Adding new route")) + for route in added: + self.configure_static_route(rbridge_id, + route['destination'], + route['nexthop']) + if removed is not None: + LOG.info(_LI("Deleting new route")) + for route in removed: + self.delete_static_route(rbridge_id, + route['destination'], + route['nexthop']) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.exception( + _LE("Failed to create static route %s"), str(e)) + else: + """ config the static route in for the VRF""" + vrf_name = template.OS_VRF_NAME.format(id=router_id) + vrf_name = vrf_name[:32] + try: + if added is not None: + LOG.info(_LI("Adding new route with VRF %s"), vrf_name) + for route in added: + self.configure_vrf_static_route(rbridge_id, + vrf_name, + route['destination'], + route['nexthop']) + if removed is not None: + LOG.info(_LI("Deleting new route from vrf %s"), vrf_name) + for route in removed: + self.delete_vrf_static_route(rbridge_id, + vrf_name, + route['destination'], + route['nexthop']) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.exception( + _LE("Failed to create static route %s"), str(e)) + + def bind_vrf_to_svi(self, rbridge_id, + vlan_id, router_id): + """binds vrf on svi""" + if not utils.is_vrf_required(): + return + vrf_name = template.OS_VRF_NAME.format(id=router_id) + vrf_name = vrf_name[:32] + try: + self.add_vrf_to_svi(rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def unbind_vrf_to_svi(self, rbridge_id, + vlan_id, router_id): + """binds vrf on svi""" + if not utils.is_vrf_required(): + return + vrf_name = template.OS_VRF_NAME.format(id=router_id) + vrf_name = vrf_name[:32] + try: + self.delete_vrf_from_svi(rbridge_id, vlan_id, vrf_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + +# L3 HA Lifecycle + def configure_protocol_vrrp(self, rbridge_id): + """enable protocol vrrp """ + try: + self.enable_protocol_vrrp(rbridge_id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def configure_vrrp_group(self, rbridge_id, + vlan_id, vrid, version): + """config vrrp virtual ip on svi""" + try: + self.create_vrrp_group(rbridge_id, vlan_id, vrid, version) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def configure_vrrp_on_svi(self, rbridge_id, vlan_id, vrid, + version, vip, advt_int, + priority=100): + """config vrrp virtual ip on svi""" + try: + self.create_vrrp_group(rbridge_id, vlan_id, vrid, version) + self.configure_vrrp_priority(rbridge_id, vlan_id, vrid, + version, priority) + self.create_vrrp_virtual_ip( + rbridge_id, vlan_id, vrid, version, vip) + self.configure_vrrp_advt_intervel(rbridge_id, vlan_id, vrid, + version, advt_int) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + +# Acl Life Cycle Management + def create_policy(self, policy_name): + """Remove Acl Policy From VDX""" + try: + self.create_acl(policy_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def create_acl_rule(self, acl): + """Remove Acl Policy From VDX""" + try: + self.create_rule(acl) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def delete_policy(self, rbridge_id, policy_name): + """Remove Acl Policy From VDX""" + try: + self.delete_acl(rbridge_id, policy_name) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def configure_policy_on_interface(self, + rbridge_id, vlan_id, name, direction): + """provision Acl Policy on VE""" + try: + self.configure_acl_on_svi(rbridge_id, vlan_id, name, direction) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def remove_policy_on_interface(self, + rbridge_id, vlan_id, name, direction): + """provision Acl Policy on VE""" + try: + self.remove_acl_on_svi(rbridge_id, vlan_id, name, direction) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + def create_vlan_interface(self, vlan_id): + """Configures a VLAN interface.""" + + confstr = template.CREATE_VLAN_INTERFACE.format(vlan_id=vlan_id) + self._edit_config('running', confstr) + + def delete_vlan_interface(self, vlan_id): + """Deletes a VLAN interface.""" + + confstr = template.DELETE_VLAN_INTERFACE.format(vlan_id=vlan_id) + self._edit_config('running', confstr) + + def create_vrf(self, rbridge_id, vrf_name): + """create vrf on rbridge.""" + + confstr = template.CREATE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + self._edit_config('running', confstr) + + def delete_vrf(self, rbridge_id, vrf_name): + """delete vrf on rbridge.""" + + confstr = template.DELETE_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name) + self._edit_config('running', confstr) + + def configure_rd_for_vrf(self, rbridge_id, vrf_name, rd): + """configure rd on vrf on rbridge.""" + + confstr = template.CONFIGURE_RD_FOR_VRF.format(rbridge_id=rbridge_id, + vrf_name=vrf_name, + rd=rd) + self._edit_config('running', confstr) + + def configure_address_family_for_vrf_v1(self, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF_V1.format( + rbridge_id=rbridge_id, vrf_name=vrf_name) + self._edit_config('running', confstr) + + def configure_address_family_for_vrf(self, rbridge_id, vrf_name): + """configure ipv4 address family to vrf on rbridge.""" + confstr = template.ADD_ADDRESS_FAMILY_FOR_VRF.format( + rbridge_id=rbridge_id, vrf_name=vrf_name) + self._edit_config('running', confstr) + + def configure_svi(self, rbridge_id, vlan_id): + """configure SVI with ip address on rbridge.""" + template.CONFIGURE_SVI.format( + rbridge_id=rbridge_id, vlan_id=vlan_id) + + def configure_svi_with_ip_address(self, rbridge_id, vlan_id, ip_address): + """configure SVI with ip address on rbridge.""" + confstr = template.CONFIGURE_SVI_WITH_IP_ADDRESS.format( + rbridge_id=rbridge_id, vlan_id=vlan_id, + ip_address=ip_address) + self._edit_config('running', confstr) + + def activate_svi(self, rbridge_id, vlan_id): + """configure SVI with ip address on rbridge.""" + if not self.is_svi_shutdown(rbridge_id, vlan_id): + return + confstr = template.ACTIVATE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + self._edit_config('running', confstr) + + def add_vrf_to_svi(self, rbridge_id, vlan_id, vrf_name): + """add vrf to svi on rbridge.""" + + confstr = template.ADD_VRF_TO_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + self._edit_config('running', confstr) + + def delete_vrf_from_svi(self, rbridge_id, vlan_id, vrf_name): + """delete vrf from svi on rbridge.""" + + confstr = template.DELETE_VRF_FROM_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrf_name=vrf_name) + self._edit_config('running', confstr) + + def remove_svi(self, rbridge_id, vlan_id): + """delete vrf from svi on rbridge.""" + confstr = template.DELETE_SVI.format(rbridge_id=rbridge_id, + vlan_id=vlan_id) + self._edit_config('running', confstr) + + def enable_protocol_vrrp(self, rbridge_id): + """enable protocol vrrp on given rbridge.""" + confstr = template.ENABLE_VRRP.format(rbridge_id=rbridge_id) + self._edit_config('running', confstr) + + def create_vrrp_group(self, rbridge_id, vlan_id, vrid, version): + """configure vrrp virtual ip on svi""" + confstr = template.CONFIGURE_VRRP_GROUP.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrid=vrid, + version=version) + confstr = self.strip_vrrp_version(confstr, version) + self._edit_config('running', confstr) + + def create_vrrp_virtual_ip(self, rbridge_id, vlan_id, vrid, version, vip): + """configure vrrp virtual ip on svi""" + confstr = template.CONFIGURE_VRRP_VIP.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + vrid=vrid, + version=version, + vip=vip) + confstr = self.strip_vrrp_version(confstr, version) + self._edit_config('running', confstr) + + def configure_vrrp_priority(self, rbridge_id, vlan_id, + vrid, version, priority=100): + """configure vrrp virtual ip on svi""" + confstr = template.CONFIGURE_VRRP_PRIORITY.format( + rbridge_id=rbridge_id, vlan_id=vlan_id, vrid=vrid, + version=version, priority=priority) + confstr = self.strip_vrrp_version(confstr, version) + self._edit_config('running', confstr) + + def configure_vrrp_advt_intervel(self, rbridge_id, vlan_id, vrid, version, + advt_intervel): + """configure vrrp virtual ip on svi""" + confstr = template.CONFIGURE_VRRP_ADVERTISEMENT_INTERVEL.format( + rbridge_id=rbridge_id, vlan_id=vlan_id, vrid=vrid, + version=version, advt_int=advt_intervel) + confstr = self.strip_vrrp_version(confstr, version) + self._edit_config('running', confstr) + + def delete_vrf_static_route(self, rbridge_id, vrf_name, dest_ip, next_hop): + configure_static_route = template.\ + DELETE_VRF_IP_STATIC_ROUTE.\ + format(rbridge_id=rbridge_id, + vrf_name=vrf_name, + destination_ip=dest_ip, + next_hop=next_hop) + try: + self._edit_config('running', configure_static_route) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW( + "Configuration of static route failed for vrf %s"), + vrf_name) + ctxt.reraise = False + + def configure_vrf_static_route( + self, rbridge_id, vrf_name, dest_ip, next_hop): + configure_static_route = template.\ + CONFIGURE_VRF_IP_STATIC_ROUTE.\ + format(rbridge_id=rbridge_id, + vrf_name=vrf_name, + destination_ip=dest_ip, + next_hop=next_hop) + try: + self._edit_config('running', configure_static_route) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning( + _LW("Configuration of static route failed for vrf %s"), + vrf_name) + ctxt.reraise = False + + def delete_static_route(self, rbridge_id, dest_ip, next_hop): + configure_static_route = template.\ + DELETE_IP_STATIC_ROUTE.\ + format(rbridge_id=rbridge_id, + destination_ip=dest_ip, + next_hop=next_hop) + try: + self._edit_config('running', configure_static_route) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("Configuration of static route failed")) + ctxt.reraise = False + + def configure_static_route(self, rbridge_id, dest_ip, next_hop): + configure_static_route = template.\ + CONFIGURE_IP_STATIC_ROUTE.\ + format(rbridge_id=rbridge_id, + destination_ip=dest_ip, + next_hop=next_hop) + try: + self._edit_config('running', configure_static_route) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("Configuration of static route failed")) + ctxt.reraise = False + + def strip_vrrp_version(self, confstr, version): + nos_version = self.osversion + if (int(nos_version[0]) >= 5): + return confstr + vrrp_version = '{0}'.format(version) + confstr = confstr.replace(vrrp_version, '') + return confstr + + def create_rule(self, confstr): + """delete Acl Policy from VDX""" + self._edit_config('running', confstr, timeout=2000) + + def create_acl(self, policy_name): + """delete Acl Policy from VDX""" + confstr = template.CREATE_ACL_POLICY.format(acl_name=policy_name) + self._edit_config('running', confstr) + + def delete_acl(self, rbridge_id, policy_name): + """delete Acl Policy from VDX""" + confstr = template.REMOVE_ACL_POLICY.format(acl_name=policy_name) + if self.is_ip_acl_exists(policy_name) and \ + not self.is_ip_acl_applied_on_any_svi(rbridge_id, + policy_name): + self._edit_config('running', confstr) + + def configure_acl_on_svi(self, rbridge_id, vlan_id, name, direction): + """delete Acl Policy from VDX""" + confstr = template.SVI_IP_ACL.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + name=name, + direction=direction) + if self.is_ip_acl_exists(name): + self._edit_config('running', confstr) + + def configure_native_vlan_on_interface(self, speed, name, vlan_id): + """configure native vlan on interface""" + confstr1 = template.ALLOW_UNTAG_TRAF_ON_INTERFACE.format(speed=speed, + name=name) + confstr2 = template.ADD_NATIVE_VLAN_TO_INTERFACE.format( + speed=speed, + name=name, + vlan_id=vlan_id) + confstr_trunk = template.CONFIGURE_INTERFACE_SWITCHPORT_TRUNK.format( + speed=speed, name=name) + self.configure_l2_mode_for_interface(speed, name) + self.configure_interface_in_trunk_mode(confstr_trunk) + self.activate_interface(speed, name) + try: + self._edit_config('running', confstr1) + except Exception: + LOG.warning(_LW("interface ready to accept untagged traffic")) + try: + self._edit_config('running', confstr2) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("Error configuring native vlan" + " on interface {}")) + ctxt.reraise = False + + def remove_native_vlan_from_interface(self, speed, name): + """configure native vlan on interface""" + confstr = template.REMOVE_NATIVE_VLAN_FROM_INTERFACE.format( + speed=speed, name=name) + try: + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("Error remove native vlan on interface {}")) + ctxt.reraise = False + + def configure_mtu_on_interface(self, speed, name, mtu): + """native vlan on interfacew""" + confstr = template.CONFIGURE_MTU_ON_INTERFACE.format( + speed=speed, + name=name, + mtu=mtu) + try: + self._edit_config('running', confstr) + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + LOG.warning(_LW("Error configuring Mtu on interface {}")) + ctxt.reraise = False + + def remove_acl_on_svi(self, rbridge_id, vlan_id, name, direction): + """delete Acl Policy from VDX""" + confstr = template.REMOVE_SVI_IP_ACL.format(rbridge_id=rbridge_id, + vlan_id=vlan_id, + name=name, + direction=direction) + if self.is_ip_acl_applied_on_svi(rbridge_id, vlan_id, name): + self._edit_config('running', confstr) + + def is_ip_acl_exists(self, name): + """checks if ip Acl exists on VDX box""" + filterstr = template.IP_ACL_NAME_XPATH_FILTER.format(name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if name in response: + return True + return False + + def is_ip_acl_applied_on_any_svi(self, rbridge_id, name): + """checks if ip acl is applied on any of svi interface""" + + filterstr = template.ACL_ON_SVIS_XPATH_FILTER.format( + rbridge_id=rbridge_id) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if name in response: + return True + return False + + def is_ip_acl_applied_on_svi(self, rbridge_id, svi, name): + """checks if ip acl is applied on given of svi interface""" + filterstr = template.ACL_ON_SVI_XPATH_FILTER.format( + rbridge_id=rbridge_id, svi=svi) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if name in response: + return True + return False + + def is_sequence_id_exists(self, name, seq_id): + """checks if sequence id configured for given Acl""" + filterstr = template.SEQ_ID_EXISTS.format(name=name, id=seq_id) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + + if name in response: + return True + return False + + def is_interface_id_exists(self, speed, name): + """checks if given interface is present""" + filterstr = template.INTERFACE_XPATH_FILTER.format(speed=speed, + name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if name in response: + return True + return False + + def is_svi_exists(self, rbridge_id, name): + """checks if given interface is present""" + filterstr = template.SVI_EXISTS_XPATH_FILTER.format( + rbridge_id=rbridge_id, name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if name in response: + return True + + def is_svi_shutdown(self, rbridge_id, name): + """checks if given interface is active""" + filterstr = template.SVI_STATUS_XPATH_FILTER.format( + rbridge_id=rbridge_id, name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if "shutdown" in response: + return True + return False + + def is_interface_shutdown(self, speed, name): + """checks if given interface is active""" + filterstr = template.INTERFACE_STATUS_XPATH_FILTER.format( + speed=speed, name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if "shutdown" in response: + return True + return False + + def is_interface_in_channel_group_mode(self, speed, name): + """checks if given interface is active""" + filterstr = template.INTERFACE_CG_STATUS_XPATH_FILTER.format( + speed=speed, name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if "channel-group" in response: + return True + return False + + def is_interface_in_port_profile_mode(self, speed, name): + """checks if given interface is active""" + filterstr = template.INTERFACE_PP_STATUS_XPATH_FILTER.format( + speed=speed, name=name) + try: + response = self._get_config('running', filterstr) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("NETCONF error")) + if "port-profile-port" in response: + return True + return False + + def set_interface_to_accept_l2_mode(self, speed, name): + + confstr = template.REMOVE_PORT_PROFILE_PORT.format(speed=speed, + name=name) + self._edit_config('running', confstr) + + @retry(RetryableException) + def nos_version_request(self, mgr): + """Get firmware information using NETCONF rpc.""" + # reply = mgr.dispatch(template.SHOW_FIRMWARE_VERSION, None, None) + # LOG.info(_LI("msg {}".format(reply))) + # et = ElementTree.fromstring(str(reply)) + # return et.find(template.NOS_VERSION).text + return "7.0.0" diff --git a/networking_brocade/vdx/non_ampp/ml2driver/utils.py b/networking_brocade/vdx/non_ampp/ml2driver/utils.py new file mode 100644 index 0000000..8e59f09 --- /dev/null +++ b/networking_brocade/vdx/non_ampp/ml2driver/utils.py @@ -0,0 +1,615 @@ +# Copyright 2016 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. +# +# Shiv Haris (shivharis@hotmail.com) +from lxml import etree +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.non_ampp.ml2driver.nos import( + nctemplates as template) +from oslo_config import cfg +from oslo_log import log as logging +from six import moves +import sys +# we will not use this for now +wellknown_dscp = {'af11': 11, 'af12': 12, 'af13': 14, + 'af21': 18, 'af22': 20, 'af23': 22, + 'af31': 26, 'af32': 28, 'af33': 30, + 'af41': 38, 'af42': 36, 'af43': 38, + 'cs1': 8, 'cs2': 16, 'cs3': 24, + 'cs4': 32, 'cs5': 40, 'cs6': 48, + 'cs7': 56, 'default': 0, 'ef': 46} + +LOG = logging.getLogger(__name__) +OBJ_PREFIX_LEN = 8 + +DEFAULT_TOPOLOGY = [] +DEFAULT_MTU = [] +DEFAULT_NATIVE_VLAN = [] +DEFAULT_RBRIDGE_RANGE = [] +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.BoolOpt('initialize_vcs', default='True', + help=_('initialize vcs')), + cfg.StrOpt('ostype', default='NOS', + help=_('OS Type of the switch')), + # cfg.StrOpt('osversion', default='autodetect', + # help=_('OS Version number')), + cfg.IntOpt('nretries', default=5, + help=_( + 'Number of retries when retieable' + 'exception occurs')), + cfg.IntOpt('ndelay', default=10, + help=_('Number of seconds to sleep')), + cfg.IntOpt('nbackoff', default=2, + help=_('backoff interval')), + cfg.BoolOpt('fqdn', default=False, + help=_( + 'Fully qualified Domain names will be used')), + cfg.BoolOpt('lacp_enabled', default=False, + help=_('lacp enabled')), + cfg.BoolOpt('remove_ch_grp', default=False, + help=_( + 'initialization stage remove channel group')), + cfg.StrOpt('port_channels', default='1024:1152', + help=_('Allowed port channel number for openstack')), + cfg.StrOpt('po_speed', default='10000', + help=_('port channel speed')), + cfg.StrOpt('port_channel_mode', default='on', + help=_('port-channel mode')), + cfg.StrOpt('port_channel_type', default='type', + help=_('port-channel type')), + cfg.StrOpt('port_channel_lb_mode', + default='src-dst-ip-mac-vid-port', + help=_('port-channel LB mode')), + ] + +TOPOLOGY_OPTS = [cfg.ListOpt('connections', default=DEFAULT_TOPOLOGY, + help=_('topology info :' + ':' + ':')), + cfg.ListOpt('bond_mappings', default=DEFAULT_TOPOLOGY, + help=_('bond mapping to port-channel' + '::' + '')), + cfg.ListOpt('mtu', default=DEFAULT_MTU, + help=_('MTU options to interface' + '::')), + cfg.ListOpt('native_vlans', default=DEFAULT_NATIVE_VLAN, + help=_('native for interface' + '::')) + ] + +RBRIDGE_OPTS = [cfg.ListOpt('rbridge_ids', default=DEFAULT_RBRIDGE_RANGE, + help=_('rbridges range 1,2,3')), + cfg.StrOpt('redundancy', default='disabled', + help=_('enable/disable L3 redundancy')), + cfg.BoolOpt('is_vrf_required', default=True, + help=_('VRFs will be created if True')), + cfg.StrOpt('vrrp_version', default='2', + help=_('vrrp version to be used')), + cfg.StrOpt('vrrp_group_id', default='100', + help=_('vrrp group to be used')), + cfg.IntOpt('vrrp_advertisement_interval', default=1, + help=_('vrrp advertisement interval')) + ] + +FWAAS = [cfg.StrOpt('seq_ids', default='1:50000', + help=_('seq ids to be used in acl rule')), + cfg.StrOpt('direction', default='both', + help=_('direction for acl to be applied')), + cfg.BoolOpt('count', default=False, + help=_('count the number of times acls are hit')), + cfg.BoolOpt('log', default=False, + help=_('count the number of times acls are hit')), + cfg.StrOpt('acl_file', default='/etc/neutron/acl.json', + help=_('pre acls and post acls on svi')) + ] + + +def register_brocade_credentials(): + cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade") + + +def reigister_brocade_topology(): + cfg.CONF.register_opts(TOPOLOGY_OPTS, "TOPOLOGY") + + +def register_brocade_l3_config(): + cfg.CONF.register_opts(RBRIDGE_OPTS, "svi") + + +def register_brocade_fwaas_config(): + cfg.CONF.register_opts(FWAAS, "fwaas") + + +def get_brocade_fwaas_config(): + register_brocade_fwaas_config() + fwaas = {'seq_ids': cfg.CONF.fwaas.seq_ids, + 'direction': cfg.CONF.fwaas.direction, + 'count': cfg.CONF.fwaas.count, + 'log': cfg.CONF.fwaas.log, + 'acl_file': cfg.CONF.fwaas.acl_file + } + return fwaas + + +def get_acl_files(): + fwaas = get_brocade_fwaas_config() + return fwaas['acl_file'] + + +def get_brocade_credentials(): + register_brocade_credentials() + switch = {'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password, + 'os': cfg.CONF.ml2_brocade.ostype, + 'osversion': '5.0.0'} + return switch + + +def get_lacp_args(): + register_brocade_credentials() + po = cfg.CONF.ml2_brocade.port_channels + po_lo, po_hi = po.split(':') + lacp = {'lacp_enabled': cfg.CONF.ml2_brocade.lacp_enabled, + 'po_lo': po_lo, + 'po_hi': po_hi, + 'po_mode': cfg.CONF.ml2_brocade.port_channel_mode, + 'po_type': cfg.CONF.ml2_brocade.port_channel_type, + 'po_lb_mode': cfg.CONF.ml2_brocade.port_channel_lb_mode, + 'po_speed': cfg.CONF.ml2_brocade.po_speed, + 'remove_ch_grp': cfg.CONF.ml2_brocade.remove_ch_grp} + return lacp + + +def _is_lacp_enabled(): + return get_lacp_args()['lacp_enabled'] + + +def get_port_channel_lo_hi(): + lacp = get_lacp_args() + return int(lacp['po_lo']), int(lacp['po_hi']) + + +def is_fqdn_supported(): + register_brocade_credentials() + return cfg.CONF.ml2_brocade.fqdn + + +def get_vcs_initialize(): + register_brocade_credentials() + return cfg.CONF.ml2_brocade.initialize_vcs + + +def get_retry_args(): + register_brocade_credentials() + return cfg.CONF.ml2_brocade.nretries,\ + cfg.CONF.ml2_brocade.ndelay,\ + cfg.CONF.ml2_brocade.nbackoff + + +def get_physical_networks(): + register_brocade_credentials() + return cfg.CONF.ml2_brocade.physical_networks + + +def get_brocade_l3_config(): + register_brocade_l3_config() + svi = {'rbridge_ids': cfg.CONF.svi.rbridge_ids, + 'redundancy': True if ((cfg.CONF.svi.redundancy == 'enabled') and + (len(cfg.CONF.svi.rbridge_ids) > 1)) + else False, + 'vrrp_version': cfg.CONF.svi.vrrp_version, + 'vrrp_group_id': cfg.CONF.svi.vrrp_group_id, + 'vrrp_advertisement_interval': + cfg.CONF.svi.vrrp_advertisement_interval, + 'is_vrf_required': cfg.CONF.svi.is_vrf_required + } + + if not svi['redundancy'] and len(svi['rbridge_ids']) > 1: + # redundancy disabled consider only first rbride configured + del svi['rbridge_ids'][1:] + LOG.info(_LI("rbridge_ids %(rbridge_ids)s" + " redundancy %(redundancy)s" + " vrrp_version %(vrrp_version)s" + " vrrp_group_id %(vrrp_group_id)s") % + {'rbridge_ids': svi['rbridge_ids'], + 'redundancy': svi['redundancy'], + 'vrrp_version': svi['vrrp_version'], + 'vrrp_group_id': svi['vrrp_group_id']}) + + return svi + + +def is_vrf_required(): + register_brocade_l3_config() + return cfg.CONF.svi.is_vrf_required + + +def remove_from_xml_tree(the_config, tag): + """removed unused xml tag""" + for elt in the_config.iterdescendants(): + if tag in elt.tag: + elt.getparent().remove(elt) + + +def add_text_to_ele(elt, text): + """add text to xml tag""" + elt.text = text + + +def remove_unused_tags(the_config, name, action, protocol, src_ip, dst_ip, + sport_operator, src_port, dport_operator, dst_port, + count, log, dscp): + """This function removes unused xml tags gor the given paramaters""" + if not dscp or dscp == '': + remove_from_xml_tree(the_config, 'dscp') +# handle count and log tags + if not count: + remove_from_xml_tree(the_config, 'count') + + if not log: + remove_from_xml_tree(the_config, 'log') +# handle ip tags + if src_ip == 'any': + remove_from_xml_tree(the_config, 'src-mask') + + if dst_ip == 'any': + remove_from_xml_tree(the_config, 'dst-mask') + +# handle protocol tags + if protocol == 'tcp': + remove_from_xml_tree(the_config, 'udp') + elif protocol == 'udp': + remove_from_xml_tree(the_config, 'tcp') + + if ((src_port == '') & (dst_port == '')): + remove_from_xml_tree(the_config, 'port') + elif (src_port == ''): + remove_from_xml_tree(the_config, 'sport') + elif (dst_port == ''): + remove_from_xml_tree(the_config, 'dport') + + if (sport_operator == 'range'): + remove_from_xml_tree(the_config, 'sport-number-eq-neq') + elif (sport_operator == 'eq'): + remove_from_xml_tree(the_config, 'sport-number-range') + else: + remove_from_xml_tree(the_config, 'sport') + + if (dport_operator == 'range'): + remove_from_xml_tree(the_config, 'dport-number-eq-neq') + elif (dport_operator == 'eq'): + remove_from_xml_tree(the_config, 'dport-number-range') + else: + remove_from_xml_tree(the_config, 'dport') + + +class SeqIdBitmap(object): + + """This class manages generating sequence ids for ip acls""" + + def __init__(self, min_search_seqid, max_search_seqid): + self._min_search_seqid = min_search_seqid + self._max_search_seqid = max_search_seqid + + def get_seq_ids(self, acl, howmany): + """Try to get a specific vlan if requested or get the next vlan.""" + ids = [] + it = 0 + for seq_id in moves.range(self._min_search_seqid, + self._max_search_seqid): + ids.append(seq_id) + it += 1 + if(it >= howmany): + break + return ids + + +def get_firewall_object_prefix(fw): + """Get Acl policy name using firewal id and tenant_id""" + policy_name = template.ROUTER_OBJ_PREFIX +\ + fw['tenant_id'][:OBJ_PREFIX_LEN] +\ + fw['id'][:OBJ_PREFIX_LEN] + return policy_name + + +def make_rule(name, seq_id, action, protocol, src_ip, src_mask, dst_ip, + dst_mask, sport_operator, sport_low, sport_high, + dport_operator, dport_low, dport_high, count, log, dscp): + """create xml template to create acl rule on VDX""" + xml_tring = template.IP_ACL_RULE.format() + the_config = etree.fromstring(xml_tring) + remove_unused_tags(the_config, name, action, protocol, src_ip, dst_ip, + sport_operator, (sport_low, sport_high), dport_operator, + (dport_low, dport_high), count, log, dscp) + + for elt in the_config.iterdescendants(): + if elt.tag == ('seq-id'): + add_text_to_ele(elt, seq_id) + elif elt.tag == ('action'): + add_text_to_ele(elt, action) + elif elt.tag == ('protocol-type'): + add_text_to_ele(elt, protocol) + elif elt.tag == ('src-host-any-sip'): + add_text_to_ele(elt, src_ip) + elif elt.tag == ('src-mask'): + add_text_to_ele(elt, src_mask) + elif elt.tag == ('dst-host-any-dip'): + add_text_to_ele(elt, dst_ip) + elif elt.tag == ('dst-mask'): + add_text_to_ele(elt, dst_mask) + elif elt.tag == ('sport'): + add_text_to_ele(elt, sport_operator) + elif "sport-number-eq-neq" in elt.tag: + add_text_to_ele(elt, sport_low) + elif "sport-number-range-lower" in elt.tag: + add_text_to_ele(elt, sport_low) + elif "sport-number-range-higher" in elt.tag: + add_text_to_ele(elt, sport_high) + elif elt.tag == ('dport'): + add_text_to_ele(elt, dport_operator) + elif "dport-number-eq-neq" in elt.tag: + add_text_to_ele(elt, dport_low) + elif "dport-number-range-lower" in elt.tag: + add_text_to_ele(elt, dport_low) + elif "dport-number-range-higher" in elt.tag: + add_text_to_ele(elt, dport_high) + elif "dscp" in elt.tag: + add_text_to_ele(elt, dscp) + + xml_request = etree.tostring(the_config, pretty_print=True) + return xml_request + + +def len_to_wild_mask(len): + """Convert a bit length to a dotted netmask (aka. CIDR to netmask)""" + mask = '' + if not isinstance(len, int) or len < 0 or len > 32: + return None + + for t in range(4): + if len > 7: + mask += '0.' + else: + dec = ((255 - (2 ** (8 - len) - 1)) ^ 255) + mask += str(dec) + '.' + len -= 8 + if len < 0: + len = 0 + + return mask[:-1] + + +def cidr_2_nwm(addr): + """Get network address prefix and wild mask from a given address.""" + if addr is None: + return (None, None) + nw_addr, nw_len = addr.split('/') + nw_len = len_to_wild_mask(int(nw_len)) + return nw_addr, nw_len + + +def get_seq_ids(seq_ids): + if seq_ids: + if ':' in seq_ids: + seq_id_low, seq_id_high = seq_ids.split(':') + return seq_id_low, seq_id_high + return None, None + + +def get_ports(port): + port_low = '' + port_high = '' + if ((port is None) | (port == '')): + return (port_low, port_high) + if ':' in port: + port_low, port_high = port.split(':') + else: + port_low, port_high = port, port_high + return port_low, port_high + + +def get_port_operator(port_low, port_high): + """detect if user has entered single port or range of ports""" + + if ((port_low) and (port_high)): + return "range" + elif((port_low) or (port_high)): + return "eq" + else: + return None + + +def _parse_info_entry(info): + """parses string ::info""" + entry = info.strip() + if ':' in entry: + try: + speed, port, info = entry.split(':') + speed = _get_long_speed(speed) + speed_port = (speed, port) + return speed_port, info + except Exception: + raise Exception("Brocade Plugin raised exception parsing port info" + "Failed") + return entry, None + + +def _parse_connection_entry(connection): + """parses string :::""" + entry = connection.strip() + if ':' in entry: + try: + host, network, speed, port = entry.split(':') + speed = _get_long_speed(speed) + speed_port = (speed, port) + return (host, network), speed_port + except Exception: + raise Exception("Brocade Plugin raised exception parsing topology" + "Failed") + return entry, None + + +def _parse_connection_info(): + """parses connection info""" + reigister_brocade_topology() + connections_info = cfg.CONF.TOPOLOGY.connections + mtu_info = cfg.CONF.TOPOLOGY.mtu + native_vlan_info = cfg.CONF.TOPOLOGY.native_vlans + device_dict = {} + bond_mappings = {} + mtu_dict = {} + native_vlans_dict = {} + for entry in connections_info: + host_physnet, speed_port = _parse_connection_entry(entry) + device_dict.setdefault(host_physnet, []).append(speed_port) + + for entry in native_vlan_info: + try: + speed_port, native_vlan = _parse_info_entry(entry) + native_vlans_dict[speed_port] = native_vlan + except Exception: + raise Exception("parsing native vlan Failed") + + for entry in mtu_info: + try: + speed_port, mtu = _parse_info_entry(entry) + mtu_dict[speed_port] = mtu + except Exception: + raise Exception("parsing MTU Failed") + + if _is_lacp_enabled(): + bond_info = cfg.CONF.TOPOLOGY.bond_mappings + for entry in bond_info: + entry = entry.strip() + if ':' in entry: + try: + host, network, port_channel = entry.split(':') + bond_mappings.setdefault((host, network), []).append( + port_channel) + except Exception: + raise Exception("parsing bond to portchannel" + " mapping failed") + return device_dict, bond_mappings, mtu_dict, native_vlans_dict + + +def _aggregate_nics_to_support_lacp(topology, bond_info): + device_dict = {} + lacp_ports = {} + if not bond_info: + po_lo, po_hi = get_port_channel_lo_hi() + for host_physnet in topology.keys(): + if len(topology[host_physnet]) >= 1: + for item in topology[host_physnet]: + if ((not bond_info) and (po_hi >= po_lo)): + lacp_ports.setdefault(po_lo, []).append(item) + LOG.debug("po lo %d po_hi %d", po_lo, po_hi) + elif bond_info: + # only one port-channel + po_lo = bond_info[host_physnet][0] + lacp_ports.setdefault(po_lo, []).append(item) + else: + LOG.error(_LE("exhausted all port-channels increase" + "port-channel range or bond mappings" + " not provided")) + sys.exit(0) + + device_dict.setdefault(host_physnet, []).append(("port-channel", + str(po_lo))) + if not bond_info: + po_lo = po_lo + 1 + LOG.debug("device_dict %s lacp_ports %s", device_dict, lacp_ports) + return device_dict, lacp_ports + + +def _get_interface_speed_name(topology, physical_network): + """given a physical network return interface speed and name""" + interfaces = [] + for key in topology.keys(): + host, network = key + if network == physical_network: + interfaces.append(topology[key]) + return interfaces + + +def _is_valid_three_tupple(interface): + """verify if given interface is threee tupple""" + if '/' in interface: + s = interface.split('/') + # Length is checked against three because of rbridge,slot,port + if len(s) != 3: + LOG.error(_LE("_is_valid_three_tupple:" + "invalid interface %s configure" + "valid interface"), interface) + return False + return True + return False + + +def _get_long_speed(short_speed): + if 'Te' in short_speed: + return "tengigabitethernet" + + elif 'Gi' in short_speed: + return "gigabitethernet" + + elif 'Fo' in short_speed: + return "fortyGigabitEthernet" + + elif 'Hu' in short_speed: + return "hundredGigabitEthernet" + else: + return "unknown" + + +def _is_valid_interface_speed(speed): + """Check if given speed is valid""" + if 'ten' in speed: + speed = "tengigabitethernet" + return True + elif 'gig' in speed: + speed = "gigabitEthernet" + return True + elif 'for' in speed: + speed = "fortyGigabitEthernet" + return True + elif 'hun' in speed: + speed = "hundredGigabitEthernet" + return True + else: + LOG.error(_LE("_is_valid_interface_speed:invalid speed parameter %s" + " configure valid speed"), speed) + return False + + +def _is_valid_interface(device, switch, nos_driver): + """validate if given interfaces are valid""" + for key in device.keys(): + for (speed, interface) in device[key]: + if not _is_valid_three_tupple(interface): + return False + if not _is_valid_interface_speed(speed): + return False + return True diff --git a/networking_brocade/vdx/services/l3_router/l3_router_plugin.py b/networking_brocade/vdx/services/l3_router/l3_router_plugin.py index c1a43ff..99a7896 100644 --- a/networking_brocade/vdx/services/l3_router/l3_router_plugin.py +++ b/networking_brocade/vdx/services/l3_router/l3_router_plugin.py @@ -17,13 +17,13 @@ """Implentation of Brocade SVI service Plugin.""" - +from networking_brocade._i18n import _ +from networking_brocade._i18n import _LE +from networking_brocade._i18n import _LI +from networking_brocade.vdx.db import models as brocade_db from networking_brocade.vdx.ml2driver.nos import nosdriver as driver from neutron.common import constants as l3_constants -from neutron.i18n import _LE -from neutron.i18n import _LI from neutron.plugins.ml2 import db -from neutron.plugins.ml2.drivers.brocade.db import models as brocade_db from neutron.services.l3_router import l3_router_plugin as router from oslo_config import cfg from oslo_log import log as logging diff --git a/networking_brocade/vdx/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py b/networking_brocade/vdx/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py index 881f714..801b30b 100644 --- a/networking_brocade/vdx/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py +++ b/networking_brocade/vdx/tests/unit/ml2/drivers/brocade/test_brocade_l3_plugin.py @@ -16,8 +16,8 @@ # import mock +from networking_brocade._i18n import _LI from neutron.db import api as db -from neutron.i18n import _LI from neutron.tests.unit.extensions import test_l3 from oslo_config import cfg from oslo_context import context as oslo_context diff --git a/setup.cfg b/setup.cfg index a532e73..21f17da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,12 +32,18 @@ data_files = etc/neutron/plugins/brocade/brocade_mlx.ini [entry_points] +neutron.db.alembic_migrations = + networking-brocade = networking_brocade.vdx.db.migration:alembic_migrations neutron.ml2.mechanism_drivers = brocade_fi_ni = networking_brocade.mlx.ml2.fi_ni.mechanism_brocade_fi_ni:BrocadeFiNiMechanism + brocade_vdx_ampp = networking_brocade.vdx.ampp.ml2driver.mechanism_brocade:BrocadeMechanism + brocade_vdx_vlan = networking_brocade.vdx.non_ampp.ml2driver.mechanism_brocade:BrocadeMechanism + brocade_vdx_baremetal = networking_brocade.vdx.bare_metal.mechanism_brocade:BrocadeMechanism # Service Plugins neutron.service_plugins = brocade_mlx_l3 = networking_brocade.mlx.services.l3_router.brocade.l3_router_plugin.BrocadeRouterPlugin + brocade_vdx_l3 = networking_brocade.vdx.services.l3_router.l3_router_plugin.BrocadeSVIPlugin [build_sphinx] source-dir = doc/source diff --git a/tox.ini b/tox.ini index 6187198..a7d6d7a 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,7 @@ commands = python setup.py build_sphinx # E123, E125 skipped as they are invalid PEP-8. show-source = True -ignore = E125,E126,E128,E129,E265,H305,H404,H405,H703 +ignore = E125,E126,E128,E129,E265,H305,H404,H405,H703,N340,N341 builtins = _ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build