From a93314b67904035e121e2a15724e808bab0ec6d8 Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Wed, 31 Aug 2016 10:04:32 -0700 Subject: [PATCH] Add supported driver checks to Zone Manager This patch adds the supported driver checks to the Fibre Channel Zone Manager. This is the same mechanism that is in Cinder volume drivers. When a zone manager driver is marked as unsupported, the zone manager won't allow it to work unless the enable_unsupported_driver=True is added to the fc-zone-manager section in cinder.conf Change-Id: If707b9005f60dc28eb99f46929fb700a2de0f755 Implements: blueprint zonemanager-supported-driver --- cinder/exception.py | 4 + .../unit/zonemanager/test_fc_zone_manager.py | 50 +++++++++++- cinder/zonemanager/drivers/fc_zone_driver.py | 10 +++ cinder/zonemanager/fc_zone_manager.py | 80 ++++++++++++++++++- cinder/zonemanager/utils.py | 18 +++-- 5 files changed, 150 insertions(+), 12 deletions(-) diff --git a/cinder/exception.py b/cinder/exception.py index 79a763bdc8d..4db0a018c22 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -1014,6 +1014,10 @@ class FCSanLookupServiceException(CinderException): message = _("Fibre Channel SAN Lookup failure: %(reason)s") +class ZoneManagerNotInitialized(CinderException): + message = _("Fibre Channel Zone Manager not initialized") + + class BrocadeZoningCliException(CinderException): message = _("Brocade Fibre Channel Zoning CLI error: %(reason)s") diff --git a/cinder/tests/unit/zonemanager/test_fc_zone_manager.py b/cinder/tests/unit/zonemanager/test_fc_zone_manager.py index 1ff7f0c40ef..82eb8e5cc13 100644 --- a/cinder/tests/unit/zonemanager/test_fc_zone_manager.py +++ b/cinder/tests/unit/zonemanager/test_fc_zone_manager.py @@ -49,11 +49,17 @@ class TestFCZoneManager(test.TestCase): @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def setUp(self, opt_mock): super(TestFCZoneManager, self).setUp() + + def __init__(self, *args, **kwargs): + super(TestFCZoneManager, self).__init__(*args, **kwargs) + + def setup_fake_driver(self): config = conf.Configuration(None) config.fc_fabric_names = fabric_name def fake_build_driver(self): self.driver = mock.Mock(fc_zone_driver.FCZoneDriver) + self.set_initialized(True) self.mock_object(fc_zone_manager.ZoneManager, '_build_driver', fake_build_driver) @@ -61,13 +67,48 @@ class TestFCZoneManager(test.TestCase): self.zm = fc_zone_manager.ZoneManager(configuration=config) self.configuration = conf.Configuration(None) self.configuration.fc_fabric_names = fabric_name - self.driver = mock.Mock(fc_zone_driver.FCZoneDriver) - def __init__(self, *args, **kwargs): - super(TestFCZoneManager, self).__init__(*args, **kwargs) + def test_unsupported_driver_disabled(self): + config = conf.Configuration(fc_zone_manager.zone_manager_opts, + 'fc-zone-manager') + config.fc_fabric_names = fabric_name + config.enable_unsupported_driver = False + + def fake_import(self, *args, **kwargs): + fake_driver = mock.Mock(fc_zone_driver.FCZoneDriver) + fake_driver.supported = False + return fake_driver + + self.patch('oslo_utils.importutils.import_object', + fake_import) + + zm = fc_zone_manager.ZoneManager(configuration=config) + self.assertFalse(zm.driver.supported) + self.assertFalse(zm.initialized) + + def test_unsupported_driver_enabled(self): + config = conf.Configuration(None) + config.fc_fabric_names = fabric_name + + def fake_import(self, *args, **kwargs): + fake_driver = mock.Mock(fc_zone_driver.FCZoneDriver) + fake_driver.supported = False + return fake_driver + + self.patch('oslo_utils.importutils.import_object', + fake_import) + + with mock.patch( + 'cinder.volume.configuration.Configuration') as mock_config: + mock_config.return_value.zone_driver = 'test' + mock_config.return_value.enable_unsupported_driver = True + zm = fc_zone_manager.ZoneManager(configuration=config) + self.assertFalse(zm.driver.supported) + self.assertTrue(zm.initialized) @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_add_connection(self, opt_mock): + self.setup_fake_driver() with mock.patch.object(self.zm.driver, 'add_connection')\ as add_connection_mock: self.zm.driver.get_san_context.return_value = fabric_map @@ -80,6 +121,7 @@ class TestFCZoneManager(test.TestCase): @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_add_connection_error(self, opt_mock): + self.setup_fake_driver() with mock.patch.object(self.zm.driver, 'add_connection')\ as add_connection_mock: add_connection_mock.side_effect = exception.FCZoneDriverException @@ -88,6 +130,7 @@ class TestFCZoneManager(test.TestCase): @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_delete_connection(self, opt_mock): + self.setup_fake_driver() with mock.patch.object(self.zm.driver, 'delete_connection')\ as delete_connection_mock: self.zm.driver.get_san_context.return_value = fabric_map @@ -100,6 +143,7 @@ class TestFCZoneManager(test.TestCase): @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_delete_connection_error(self, opt_mock): + self.setup_fake_driver() with mock.patch.object(self.zm.driver, 'delete_connection')\ as del_connection_mock: del_connection_mock.side_effect = exception.FCZoneDriverException diff --git a/cinder/zonemanager/drivers/fc_zone_driver.py b/cinder/zonemanager/drivers/fc_zone_driver.py index 9417c965b80..7704e4409fb 100644 --- a/cinder/zonemanager/drivers/fc_zone_driver.py +++ b/cinder/zonemanager/drivers/fc_zone_driver.py @@ -44,3 +44,13 @@ class FCZoneDriver( def __init__(self, **kwargs): super(FCZoneDriver, self).__init__(**kwargs) LOG.debug("Initializing FCZoneDriver") + + # If a driver hasn't maintained their CI system, this will get set + # to False, which prevents the driver from starting. + # Add enable_unsupported_driver = True in cinder.conf to get the + # unsupported driver started. + self._supported = True + + @property + def supported(self): + return self._supported diff --git a/cinder/zonemanager/fc_zone_manager.py b/cinder/zonemanager/fc_zone_manager.py index 52c2c4a85c3..2e5b52960c3 100644 --- a/cinder/zonemanager/fc_zone_manager.py +++ b/cinder/zonemanager/fc_zone_manager.py @@ -37,7 +37,7 @@ from oslo_utils import importutils import six from cinder import exception -from cinder.i18n import _, _LI +from cinder.i18n import _, _LE, _LI, _LW from cinder.volume import configuration as config from cinder.zonemanager import fc_common import cinder.zonemanager.fczm_constants as zone_constant @@ -61,7 +61,16 @@ zone_manager_opts = [ cfg.StrOpt('fc_san_lookup_service', default='cinder.zonemanager.drivers.brocade' '.brcd_fc_san_lookup_service.BrcdFCSanLookupService', - help='FC SAN Lookup Service') + help='FC SAN Lookup Service'), + cfg.BoolOpt('enable_unsupported_driver', + default=False, + help="Set this to True when you want to allow an unsupported " + "zone manager driver to start. Drivers that haven't " + "maintained a working CI system and testing are marked " + "as unsupported until CI is working again. This also " + "marks a driver as deprecated and may be removed in the " + "next release."), + ] CONF = cfg.CONF @@ -81,6 +90,7 @@ class ZoneManager(fc_common.FCCommon): VERSION = "1.0.2" driver = None + _initialized = False fabric_names = [] def __new__(class_, *args, **kwargs): @@ -94,6 +104,7 @@ class ZoneManager(fc_common.FCCommon): self.configuration = config.Configuration(zone_manager_opts, 'fc-zone-manager') + self.set_initialized(False) self._build_driver() def _build_driver(self): @@ -102,11 +113,52 @@ class ZoneManager(fc_common.FCCommon): {'driver': zone_driver}) zm_config = config.Configuration(zone_manager_opts, 'fc-zone-manager') - # Initialize vendor specific implementation of FCZoneDriver + # Initialize vendor specific implementation of FCZoneDriver self.driver = importutils.import_object( zone_driver, configuration=zm_config) + if not self.driver.supported: + self._log_unsupported_driver_warning() + + if not self.configuration.enable_unsupported_driver: + LOG.error(_LE("Unsupported drivers are disabled." + " You can re-enable by adding " + "enable_unsupported_driver=True to the " + "fc-zone-manager section in cinder.conf"), + resource={'type': 'zone_manager', + 'id': self.__class__.__name__}) + return + + self.set_initialized(True) + + @property + def initialized(self): + return self._initialized + + def set_initialized(self, value=True): + self._initialized = value + + def _require_initialized(self): + """Verifies that the zone manager has been properly initialized.""" + if not self.initialized: + LOG.error(_LE("Fibre Channel Zone Manager is not initialized.""")) + raise exception.ZoneManagerNotInitialized() + else: + self._log_unsupported_driver_warning() + + def _log_unsupported_driver_warning(self): + """Annoy the log about unsupported fczm drivers.""" + if not self.driver.supported: + LOG.warning(_LW("Zone Manager driver (%(driver_name)s %(version)s)" + " is currently unsupported and may be removed in " + "the next release of OpenStack. Use at your own " + "risk."), + {'driver_name': self.driver.__class__.__name__, + 'version': self.driver.get_version()}, + resource={'type': 'zone_manager', + 'id': self.driver.__class__.__name__}) + def get_zoning_state_ref_count(self, initiator_wwn, target_wwn): """Zone management state check. @@ -136,6 +188,17 @@ class ZoneManager(fc_common.FCCommon): host_name = None storage_system = None + try: + # Make sure the driver is loaded and we are initialized + self._log_unsupported_driver_warning() + self._require_initialized() + except exception.ZoneManagerNotInitialized: + LOG.error(_LE("Cannot add Fibre Channel Zone because the " + "Zone Manager is not initialized properly."), + resource={'type': 'zone_manager', + 'id': self.__class__.__name__}) + return + try: initiator_target_map = ( conn_info[zone_constant.DATA][zone_constant.IT_MAP]) @@ -202,6 +265,17 @@ class ZoneManager(fc_common.FCCommon): host_name = None storage_system = None + try: + # Make sure the driver is loaded and we are initialized + self._log_unsupported_driver_warning() + self._require_initialized() + except exception.ZoneManagerNotInitialized: + LOG.error(_LE("Cannot delete fibre channel zone because the " + "Zone Manager is not initialized properly."), + resource={'type': 'zone_manager', + 'id': self.__class__.__name__}) + return + try: initiator_target_map = ( conn_info[zone_constant.DATA][zone_constant.IT_MAP]) diff --git a/cinder/zonemanager/utils.py b/cinder/zonemanager/utils.py index 7ba578f2df9..239f9ff5116 100644 --- a/cinder/zonemanager/utils.py +++ b/cinder/zonemanager/utils.py @@ -35,12 +35,18 @@ def create_zone_manager(): if config.safe_get('zoning_mode') == 'fabric': LOG.debug("FC Zone Manager enabled.") zm = fc_zone_manager.ZoneManager() - LOG.info(_LI("Using FC Zone Manager %(zm_version)s," - " Driver %(drv_name)s %(drv_version)s."), - {'zm_version': zm.get_version(), - 'drv_name': zm.driver.__class__.__name__, - 'drv_version': zm.driver.get_version()}) - return zm + if zm.initialized: + LOG.info(_LI("Using FC Zone Manager %(zm_version)s," + " Driver %(drv_name)s %(drv_version)s."), + {'zm_version': zm.get_version(), + 'drv_name': zm.driver.__class__.__name__, + 'drv_version': zm.driver.get_version()}) + return zm + else: + LOG.debug("FC Zone Manager %(zm_version)s disabled", + {"zm_version": zm.get_version()}) + return None + else: LOG.debug("FC Zone Manager not enabled in cinder.conf.") return None