From f5e58fff58deadd8843b9fc8ab76856d0452bdfa Mon Sep 17 00:00:00 2001 From: Shiv Haris Date: Fri, 20 Jun 2014 15:05:10 -0700 Subject: [PATCH] Added support for NOS version 4.1.0, 5.0.0 and greater NETCONF temaplates for NOS version greater than 4.1.0 are slightly different (argh). An init time check of the NOS version is done to enable selection of the correct NETCONF templates. Change-Id: I01e82ad402fbbb25d92a99a3325ca2608dd514cb Closes-bug: #1332719 --- etc/neutron/plugins/ml2/ml2_conf_brocade.ini | 2 + .../ml2/drivers/brocade/mechanism_brocade.py | 52 ++++++++++-- .../ml2/drivers/brocade/nos/nctemplates.py | 55 ++++++++++++ .../ml2/drivers/brocade/nos/nosdriver.py | 83 ++++++++++++++++++- .../brocade/test_brocade_mechanism_driver.py | 48 +++++++++++ 5 files changed, 234 insertions(+), 6 deletions(-) diff --git a/etc/neutron/plugins/ml2/ml2_conf_brocade.ini b/etc/neutron/plugins/ml2/ml2_conf_brocade.ini index 66987e9910..67574110b6 100644 --- a/etc/neutron/plugins/ml2/ml2_conf_brocade.ini +++ b/etc/neutron/plugins/ml2/ml2_conf_brocade.ini @@ -3,6 +3,7 @@ # password = # address = # ostype = NOS +# osversion = autodetect | n.n.n # physical_networks = physnet1,physnet2 # # Example: @@ -10,4 +11,5 @@ # password = password # address = 10.24.84.38 # ostype = NOS +# osversion = 4.1.1 # physical_networks = physnet1,physnet2 diff --git a/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py b/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py index 015921df52..92e0b2954f 100644 --- a/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py +++ b/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py @@ -39,7 +39,9 @@ ML2_BROCADE = [cfg.StrOpt('address', default='', cfg.StrOpt('physical_networks', default='', help=_('Allowed physical networks')), cfg.StrOpt('ostype', default='NOS', - help=_('Unused')) + 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") @@ -66,12 +68,52 @@ class BrocadeMechanism(driver_api.MechanismDriver): def brocade_init(self): """Brocade specific initialization for this class.""" - self._switch = {'address': cfg.CONF.ml2_brocade.address, - 'username': cfg.CONF.ml2_brocade.username, - 'password': cfg.CONF.ml2_brocade.password - } + 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 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.""" diff --git a/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py index dbf7575deb..f885ff8209 100644 --- a/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py +++ b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py @@ -23,6 +23,24 @@ 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 = """ @@ -72,6 +90,20 @@ CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """ """ +# 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 = """ @@ -185,6 +217,29 @@ xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> """ +#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} + + + +""" + # # Constants # diff --git a/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py index f647370aee..47d6330951 100644 --- a/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py +++ b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py @@ -23,6 +23,7 @@ Neutron network life-cycle management. """ from ncclient import manager +from xml.etree import ElementTree from neutron.openstack.common import excutils from neutron.openstack.common import log as logging @@ -51,6 +52,18 @@ class NOSdriver(): 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.""" @@ -69,6 +82,7 @@ class NOSdriver(): 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(_("Connect failed to switch")) @@ -83,16 +97,46 @@ class NOSdriver(): 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(_("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(_("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) - self.configure_l2_mode_for_vlan_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) @@ -104,9 +148,12 @@ class NOSdriver(): 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) @@ -234,3 +281,37 @@ class NOSdriver(): 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" diff --git a/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py index 721822dfb6..4f20f2ccc8 100644 --- a/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py +++ b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py @@ -66,3 +66,51 @@ class TestBrocadeMechDriverPortsV2(test_db_plugin.TestPortsV2, class TestBrocadeMechDriverSubnetsV2(test_db_plugin.TestSubnetsV2, 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)