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