diff --git a/etc/neutron/plugins/ml2/ml2_conf_brocade.ini b/etc/neutron/plugins/ml2/ml2_conf_brocade.ini index 66987e9910d..67574110b63 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 015921df523..a577efcab04 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 dbf7575deba..f885ff82095 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 f647370aee6..47d6330951c 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 2dac0fe2a24..226c5a0858e 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 @@ -67,3 +67,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)