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
(cherry picked from commit f807023246)
This commit is contained in:
Shiv Haris 2014-06-20 15:05:10 -07:00
parent 0ca4e59aad
commit 2ce59ec41e
5 changed files with 234 additions and 6 deletions

View File

@ -3,6 +3,7 @@
# password = <mgmt admin password>
# address = <switch mgmt ip 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

View File

@ -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."""

View File

@ -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 = """
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
@ -72,6 +90,20 @@ CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """
</config>
"""
# Configure L2 mode for VLAN sub-profile (port_profile_name)
CONFIGURE_L2_MODE_FOR_VLAN_PROFILE_IN_DOMAIN = """
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
<name>{name}</name>
<vlan-profile>
<switchport-basic>
<basic/>
</switchport-basic>
</vlan-profile>
</port-profile>
</config>
"""
# Configure L2 mode for VLAN sub-profile (port_profile_name)
CONFIGURE_L2_MODE_FOR_VLAN_PROFILE = """
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
@ -185,6 +217,29 @@ xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">
</config>
"""
#port-profile domain management commands
REMOVE_PORTPROFILE_FROM_DOMAIN = """
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<port-profile-domain xmlns="urn:brocade.com:mgmt:brocade-port-profile">
<port-profile-domain-name>{domain_name}</port-profile-domain-name>
<profile operation="delete">
<profile-name>{name}</profile-name>
</profile>
</port-profile-domain>
</config>
"""
#put port profile in default domain
CONFIGURE_PORTPROFILE_IN_DOMAIN = """
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<port-profile-domain xmlns="urn:brocade.com:mgmt:brocade-port-profile">
<port-profile-domain-name>{domain_name}</port-profile-domain-name>
<profile>
<profile-name>{name}</profile-name>
</profile>
</port-profile-domain>
</config>
"""
#
# Constants
#

View File

@ -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"

View File

@ -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)