From 824de4a01d424508922a9aa24293c9fbd27a8f54 Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Thu, 19 Jun 2014 10:35:29 -0700 Subject: [PATCH] Ensure FC ZoneManager is called This patch ensures that the FC ZoneManager is called during for all cases in Cinder that does a volume attach/detach for FC enabled drivers. The problem was that we had code in the volume manager that manually called the ZoneManager, after initialize_connection and terminate_connection, but other places in Cinder were not calling the ZoneManager. This patch creates 2 new decorators that can be used for any driver's initialize_connection and terminate_connection call. The decorator checks to make sure that the return value is for a fibre_channel attachment and then calls the ZoneManager's add_connection or delete_connection. Change-Id: Ie3ae70785f500a140003ad3a8495e0ddc3516ea8 Closes-Bug: 1321798 --- cinder/tests/fake_driver.py | 37 ++++ cinder/tests/test_volume.py | 17 +- .../tests/zonemanager/test_fc_zone_manager.py | 41 +++-- .../tests/zonemanager/test_volume_driver.py | 90 ++++++++++ .../zonemanager/test_volume_manager_fc.py | 164 ------------------ cinder/volume/drivers/emc/emc_smis_fc.py | 3 + cinder/volume/drivers/huawei/huawei_hvs.py | 3 + cinder/volume/drivers/huawei/huawei_t.py | 3 + .../drivers/ibm/storwize_svc/__init__.py | 3 + cinder/volume/drivers/san/hp/hp_3par_fc.py | 3 + cinder/volume/drivers/san/hp/hp_msa_fc.py | 3 + cinder/volume/manager.py | 62 +------ cinder/zonemanager/fc_zone_manager.py | 18 +- cinder/zonemanager/utils.py | 96 ++++++++++ 14 files changed, 290 insertions(+), 253 deletions(-) create mode 100644 cinder/tests/zonemanager/test_volume_driver.py delete mode 100644 cinder/tests/zonemanager/test_volume_manager_fc.py create mode 100644 cinder/zonemanager/utils.py diff --git a/cinder/tests/fake_driver.py b/cinder/tests/fake_driver.py index 72f713689..735608bd8 100644 --- a/cinder/tests/fake_driver.py +++ b/cinder/tests/fake_driver.py @@ -16,6 +16,7 @@ from cinder.openstack.common import log as logging from cinder.tests.brick.fake_lvm import FakeBrickLVM from cinder.volume import driver from cinder.volume.drivers import lvm +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -77,6 +78,42 @@ class FakeISERDriver(FakeISCSIDriver): return (None, None) +class FakeFibreChannelDriver(driver.FibreChannelDriver): + + @fczm_utils.AddFCZone + def initialize_connection(self, volume, connector): + return { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'initiator_target_map': {'fake_wwn': ['fake_wwn2']}, + }} + + @fczm_utils.AddFCZone + def no_zone_initialize_connection(self, volume, connector): + """This shouldn't call the ZM.""" + return { + 'driver_volume_type': 'bogus', + 'data': { + 'initiator_target_map': {'fake_wwn': ['fake_wwn2']}, + }} + + @fczm_utils.RemoveFCZone + def terminate_connection(self, volume, connector, **kwargs): + return { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'initiator_target_map': {'fake_wwn': ['fake_wwn2']}, + }} + + @fczm_utils.RemoveFCZone + def no_zone_terminate_connection(self, volume, connector, **kwargs): + return { + 'driver_volume_type': 'bogus', + 'data': { + 'initiator_target_map': {'fake_wwn': ['fake_wwn2']}, + }} + + class LoggingVolumeDriver(driver.VolumeDriver): """Logs and records calls, for unit tests.""" diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py index f22ac1f16..c7b44b1d2 100644 --- a/cinder/tests/test_volume.py +++ b/cinder/tests/test_volume.py @@ -3530,14 +3530,9 @@ class FibreChannelTestCase(DriverTestCase): """Test Case for FibreChannelDriver.""" driver_name = "cinder.volume.driver.FibreChannelDriver" - def setUp(self): - super(FibreChannelTestCase, self).setUp() - self.driver = driver.FibreChannelDriver() - self.driver.do_setup(None) - def test_initialize_connection(self): self.assertRaises(NotImplementedError, - self.driver.initialize_connection, {}, {}) + self.volume.driver.initialize_connection, {}, {}) def test_validate_connector(self): """validate_connector() successful use case. @@ -3547,33 +3542,33 @@ class FibreChannelTestCase(DriverTestCase): """ connector = {'wwpns': ["not empty"], 'wwnns': ["not empty"]} - self.driver.validate_connector(connector) + self.volume.driver.validate_connector(connector) def test_validate_connector_no_wwpns(self): """validate_connector() throws exception when it has no wwpns.""" connector = {'wwnns': ["not empty"]} self.assertRaises(exception.VolumeDriverException, - self.driver.validate_connector, connector) + self.volume.driver.validate_connector, connector) def test_validate_connector_empty_wwpns(self): """validate_connector() throws exception when it has empty wwpns.""" connector = {'wwpns': [], 'wwnns': ["not empty"]} self.assertRaises(exception.VolumeDriverException, - self.driver.validate_connector, connector) + self.volume.driver.validate_connector, connector) def test_validate_connector_no_wwnns(self): """validate_connector() throws exception when it has no wwnns.""" connector = {'wwpns': ["not empty"]} self.assertRaises(exception.VolumeDriverException, - self.driver.validate_connector, connector) + self.volume.driver.validate_connector, connector) def test_validate_connector_empty_wwnns(self): """validate_connector() throws exception when it has empty wwnns.""" connector = {'wwnns': [], 'wwpns': ["not empty"]} self.assertRaises(exception.VolumeDriverException, - self.driver.validate_connector, connector) + self.volume.driver.validate_connector, connector) class VolumePolicyTestCase(test.TestCase): diff --git a/cinder/tests/zonemanager/test_fc_zone_manager.py b/cinder/tests/zonemanager/test_fc_zone_manager.py index 909e80069..656630f50 100644 --- a/cinder/tests/zonemanager/test_fc_zone_manager.py +++ b/cinder/tests/zonemanager/test_fc_zone_manager.py @@ -25,7 +25,7 @@ from cinder import exception from cinder import test from cinder.volume import configuration as conf from cinder.zonemanager.drivers.fc_zone_driver import FCZoneDriver -from cinder.zonemanager.fc_zone_manager import ZoneManager +from cinder.zonemanager import fc_zone_manager from mock import Mock fabric_name = 'BRCD_FAB_3' @@ -34,45 +34,52 @@ fabric_map = {'BRCD_FAB_3': ['20240002ac000a50']} target_list = ['20240002ac000a50'] -class TestFCZoneManager(ZoneManager, test.TestCase): +class TestFCZoneManager(test.TestCase): def setUp(self): super(TestFCZoneManager, self).setUp() - self.configuration = conf.Configuration(None) - self.configuration.set_default('fc_fabric_names', fabric_name) - self.driver = Mock(FCZoneDriver) + config = conf.Configuration(None) + config.fc_fabric_names = fabric_name + + def fake_build_driver(self): + self.driver = Mock(FCZoneDriver) + + self.stubs.Set(fc_zone_manager.ZoneManager, '_build_driver', + fake_build_driver) + + self.zm = fc_zone_manager.ZoneManager(configuration=config) def __init__(self, *args, **kwargs): test.TestCase.__init__(self, *args, **kwargs) def test_add_connection(self): - with mock.patch.object(self.driver, 'add_connection')\ + with mock.patch.object(self.zm.driver, 'add_connection')\ as add_connection_mock: - self.driver.get_san_context.return_value = fabric_map - self.add_connection(init_target_map) - self.driver.get_san_context.assert_called_once(target_list) + self.zm.driver.get_san_context.return_value = fabric_map + self.zm.add_connection(init_target_map) + self.zm.driver.get_san_context.assert_called_once(target_list) add_connection_mock.assert_called_once_with(fabric_name, init_target_map) def test_add_connection_error(self): - with mock.patch.object(self.driver, 'add_connection')\ + with mock.patch.object(self.zm.driver, 'add_connection')\ as add_connection_mock: add_connection_mock.side_effect = exception.FCZoneDriverException self.assertRaises(exception.ZoneManagerException, - self.add_connection, init_target_map) + self.zm.add_connection, init_target_map) def test_delete_connection(self): - with mock.patch.object(self.driver, 'delete_connection')\ + with mock.patch.object(self.zm.driver, 'delete_connection')\ as delete_connection_mock: - self.driver.get_san_context.return_value = fabric_map - self.delete_connection(init_target_map) - self.driver.get_san_context.assert_called_once_with(target_list) + self.zm.driver.get_san_context.return_value = fabric_map + self.zm.delete_connection(init_target_map) + self.zm.driver.get_san_context.assert_called_once_with(target_list) delete_connection_mock.assert_called_once_with(fabric_name, init_target_map) def test_delete_connection_error(self): - with mock.patch.object(self.driver, 'delete_connection')\ + with mock.patch.object(self.zm.driver, 'delete_connection')\ as del_connection_mock: del_connection_mock.side_effect = exception.FCZoneDriverException self.assertRaises(exception.ZoneManagerException, - self.delete_connection, init_target_map) + self.zm.delete_connection, init_target_map) diff --git a/cinder/tests/zonemanager/test_volume_driver.py b/cinder/tests/zonemanager/test_volume_driver.py new file mode 100644 index 000000000..6f9751888 --- /dev/null +++ b/cinder/tests/zonemanager/test_volume_driver.py @@ -0,0 +1,90 @@ +# (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Copyright 2014 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + + +"""Unit tests for Volume Manager.""" + +import mock + +from cinder import test +from cinder.tests import fake_driver +from cinder import utils +from cinder.volume import configuration as conf +from cinder.zonemanager.drivers.brocade import brcd_fc_zone_driver +from cinder.zonemanager import fc_zone_manager + + +class TestVolumeDriver(test.TestCase): + + def setUp(self): + super(TestVolumeDriver, self).setUp() + self.driver = fake_driver.FakeFibreChannelDriver() + brcd_fc_zone_driver.BrcdFCZoneDriver = mock.Mock() + self.addCleanup(self._cleanup) + + def _cleanup(self): + self.driver = None + + def __init__(self, *args, **kwargs): + test.TestCase.__init__(self, *args, **kwargs) + + @mock.patch.object(utils, 'require_driver_initialized') + def test_initialize_connection_with_decorator(self, utils_mock): + utils_mock.return_value = True + with mock.patch.object(fc_zone_manager.ZoneManager, 'add_connection')\ + as add_zone_mock: + with mock.patch.object(conf.Configuration, 'safe_get')\ + as mock_safe_get: + mock_safe_get.return_value = 'fabric' + conn_info = self.driver.initialize_connection(None, None) + init_target_map = conn_info['data']['initiator_target_map'] + add_zone_mock.assert_called_once_with(init_target_map) + + @mock.patch.object(utils, 'require_driver_initialized') + def test_initialize_connection_no_decorator(self, utils_mock): + utils_mock.return_value = True + with mock.patch.object(fc_zone_manager.ZoneManager, 'add_connection')\ + as add_zone_mock: + with mock.patch.object(conf.Configuration, 'safe_get')\ + as mock_safe_get: + mock_safe_get.return_value = 'fabric' + self.driver.no_zone_initialize_connection(None, None) + assert not add_zone_mock.called + + @mock.patch.object(utils, 'require_driver_initialized') + def test_terminate_connection_with_decorator(self, utils_mock): + utils_mock.return_value = True + with mock.patch.object(fc_zone_manager.ZoneManager, + 'delete_connection') as remove_zone_mock: + with mock.patch.object(conf.Configuration, 'safe_get')\ + as mock_safe_get: + mock_safe_get.return_value = 'fabric' + conn_info = self.driver.terminate_connection(None, None) + init_target_map = conn_info['data']['initiator_target_map'] + remove_zone_mock.assert_called_once_with(init_target_map) + + @mock.patch.object(utils, 'require_driver_initialized') + def test_terminate_connection_no_decorator(self, utils_mock): + utils_mock.return_value = True + with mock.patch.object(fc_zone_manager.ZoneManager, + 'delete_connection') as remove_zone_mock: + with mock.patch.object(conf.Configuration, 'safe_get')\ + as mock_safe_get: + mock_safe_get.return_value = 'fabric' + self.driver.no_zone_terminate_connection(None, None) + assert not remove_zone_mock.called diff --git a/cinder/tests/zonemanager/test_volume_manager_fc.py b/cinder/tests/zonemanager/test_volume_manager_fc.py deleted file mode 100644 index 5cc5a7078..000000000 --- a/cinder/tests/zonemanager/test_volume_manager_fc.py +++ /dev/null @@ -1,164 +0,0 @@ -# (c) Copyright 2014 Brocade Communications Systems Inc. -# All Rights Reserved. -# -# Copyright 2014 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - - -"""Unit tests for Volume Manager.""" - -import mock - -from cinder import exception -from cinder import test -from cinder import utils -from cinder.volume import configuration as conf -from cinder.volume import driver -from cinder.volume import manager -from cinder.zonemanager import fc_zone_manager - -init_target_map = {'10008c7cff523b01': ['20240002ac000a50']} -conn_info = { - 'driver_volume_type': 'fibre_channel', - 'data': { - 'target_discovered': True, - 'target_lun': 1, - 'target_wwn': '20240002ac000a50', - 'initiator_target_map': { - '10008c7cff523b01': ['20240002ac000a50'] - } - } -} -conn_info_no_init_target_map = { - 'driver_volume_type': 'fibre_channel', - 'data': { - 'target_discovered': True, - 'target_lun': 1, - 'target_wwn': '20240002ac000a50', - } -} - - -class TestVolumeManager(manager.VolumeManager, test.TestCase): - - def setUp(self): - super(TestVolumeManager, self).setUp() - self.configuration = conf.Configuration(None) - self.configuration.set_default('fc_fabric_names', 'BRCD_FAB_4', - 'fc-zone-manager') - self.configuration.zoning_mode = 'fabric' - self.driver = mock.Mock(driver.VolumeDriver) - self.driver.initialize_connection.return_value = conn_info - self.driver.terminate_connection.return_value = conn_info - self.driver.create_export.return_value = None - self.db = mock.Mock() - self.db.volume_get.return_value = {'volume_type_id': None} - self.db.volume_admin_metadata_get.return_value = {} - self.context_mock = mock.Mock() - self.context_mock.elevated.return_value = None - self.zonemanager = fc_zone_manager.ZoneManager( - configuration=self.configuration) - - def tearDown(self): - super(TestVolumeManager, self).tearDown() - self.configuration = None - self.db = None - self.driver = None - self.zonemanager = None - - def __init__(self, *args, **kwargs): - test.TestCase.__init__(self, *args, **kwargs) - - @mock.patch.object(utils, 'require_driver_initialized') - def test_initialize_connection_voltype_fc_mode_fabric(self, - utils_mock): - utils_mock.return_value = True - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - self.initialize_connection(self.context_mock, None, None) - add_del_conn_mock.assert_called_once_with(conn_info, 1) - - @mock.patch.object(utils, 'require_driver_initialized') - def test_initialize_connection_voltype_fc_mode_none(self, - utils_mock): - utils_mock.return_value = True - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - self.configuration.zoning_mode = 'none' - self.zonemanager = None - self.initialize_connection(self.context_mock, None, None) - assert not add_del_conn_mock.called - - def test_terminate_connection_exception(self): - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - add_del_conn_mock.side_effect = exception.ZoneManagerException - self.assertRaises(exception.VolumeBackendAPIException, - self.terminate_connection, None, None, None, - False) - - @mock.patch.object(utils, 'require_driver_initialized') - def test_terminate_connection_voltype_fc_mode_fabric(self, - utils_mock): - utils_mock.return_value = True - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - self.terminate_connection(self.context_mock, None, None, False) - add_del_conn_mock.assert_called_once_with(conn_info, 0) - - @mock.patch.object(utils, 'require_driver_initialized') - def test_terminate_connection_mode_none(self, - utils_mock): - utils_mock.return_value = True - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - self.configuration.zoning_mode = 'none' - self.zonemanager = None - self.terminate_connection(self.context_mock, None, None, False) - assert not add_del_conn_mock.called - - @mock.patch.object(utils, 'require_driver_initialized') - def test_terminate_connection_conn_info_none(self, - utils_mock): - utils_mock.return_value = True - self.driver.terminate_connection.return_value = None - with mock.patch.object(manager.VolumeManager, - '_add_or_delete_fc_connection')\ - as add_del_conn_mock: - self.terminate_connection(self.context_mock, None, None, False) - assert not add_del_conn_mock.called - - @mock.patch.object(fc_zone_manager.ZoneManager, 'add_connection') - def test__add_or_delete_connection_add(self, - add_connection_mock): - self._add_or_delete_fc_connection(conn_info, 1) - add_connection_mock.assert_called_once_with(init_target_map) - - @mock.patch.object(fc_zone_manager.ZoneManager, 'delete_connection') - def test__add_or_delete_connection_delete(self, - delete_connection_mock): - self._add_or_delete_fc_connection(conn_info, 0) - delete_connection_mock.assert_called_once_with(init_target_map) - - @mock.patch.object(fc_zone_manager.ZoneManager, 'delete_connection') - def test__add_or_delete_connection_no_init_target_map(self, - del_conn_mock): - self._add_or_delete_fc_connection(conn_info_no_init_target_map, 0) - assert not del_conn_mock.called diff --git a/cinder/volume/drivers/emc/emc_smis_fc.py b/cinder/volume/drivers/emc/emc_smis_fc.py index e15f2b140..cd39777bf 100644 --- a/cinder/volume/drivers/emc/emc_smis_fc.py +++ b/cinder/volume/drivers/emc/emc_smis_fc.py @@ -21,6 +21,7 @@ from cinder import context from cinder.openstack.common import log as logging from cinder.volume import driver from cinder.volume.drivers.emc import emc_smis_common +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -118,6 +119,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver): """Make sure volume is exported.""" pass + @fczm_utils.AddFCZone def initialize_connection(self, volume, connector): """Initializes the connection and returns connection info. @@ -168,6 +170,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver): return data + @fczm_utils.RemoveFCZone def terminate_connection(self, volume, connector, **kwargs): """Disallow connection from connector.""" self.common.terminate_connection(volume, connector) diff --git a/cinder/volume/drivers/huawei/huawei_hvs.py b/cinder/volume/drivers/huawei/huawei_hvs.py index afaf2ca72..098346561 100644 --- a/cinder/volume/drivers/huawei/huawei_hvs.py +++ b/cinder/volume/drivers/huawei/huawei_hvs.py @@ -19,6 +19,7 @@ Volume Drivers for Huawei OceanStor HVS storage arrays. from cinder.volume import driver from cinder.volume.drivers.huawei.rest_common import HVSCommon +from cinder.zonemanager import utils as fczm_utils class HuaweiHVSISCSIDriver(driver.ISCSIDriver): @@ -150,10 +151,12 @@ class HuaweiHVSFCDriver(driver.FibreChannelDriver): data['driver_version'] = self.VERSION return data + @fczm_utils.AddFCZone def initialize_connection(self, volume, connector): """Map a volume to a host.""" return self.common.initialize_connection_fc(volume, connector) + @fczm_utils.RemoveFCZone def terminate_connection(self, volume, connector, **kwargs): """Terminate the map.""" self.common.terminate_connection(volume, connector, **kwargs) diff --git a/cinder/volume/drivers/huawei/huawei_t.py b/cinder/volume/drivers/huawei/huawei_t.py index 9152afd75..b88e5e90a 100644 --- a/cinder/volume/drivers/huawei/huawei_t.py +++ b/cinder/volume/drivers/huawei/huawei_t.py @@ -25,6 +25,7 @@ from cinder.openstack.common import log as logging from cinder.volume import driver from cinder.volume.drivers.huawei import huawei_utils from cinder.volume.drivers.huawei import ssh_common +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -438,6 +439,7 @@ class HuaweiTFCDriver(driver.FibreChannelDriver): LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) + @fczm_utils.AddFCZone def initialize_connection(self, volume, connector): """Create FC connection between a volume and a host.""" LOG.debug('initialize_connection: volume name: %(vol)s, ' @@ -547,6 +549,7 @@ class HuaweiTFCDriver(driver.FibreChannelDriver): def _get_fc_port_ctr(self, port_details): return port_details['ControllerID'] + @fczm_utils.RemoveFCZone def terminate_connection(self, volume, connector, **kwargs): """Terminate the map.""" LOG.debug('terminate_connection: volume: %(vol)s, host: %(host)s, ' diff --git a/cinder/volume/drivers/ibm/storwize_svc/__init__.py b/cinder/volume/drivers/ibm/storwize_svc/__init__.py index 471ddad33..9a7230d77 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/__init__.py +++ b/cinder/volume/drivers/ibm/storwize_svc/__init__.py @@ -49,6 +49,7 @@ from cinder import utils from cinder.volume.drivers.ibm.storwize_svc import helpers as storwize_helpers from cinder.volume.drivers.san import san from cinder.volume import volume_types +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -294,6 +295,7 @@ class StorwizeSVCDriver(san.SanDriver): return self._helpers.get_vdisk_params(self.configuration, self._state, type_id, volume_type=volume_type) + @fczm_utils.AddFCZone @utils.synchronized('storwize-host', external=True) def initialize_connection(self, volume, connector): """Perform the necessary work so that an iSCSI/FC connection can @@ -464,6 +466,7 @@ class StorwizeSVCDriver(san.SanDriver): return i_t_map + @fczm_utils.RemoveFCZone @utils.synchronized('storwize-host', external=True) def terminate_connection(self, volume, connector, **kwargs): """Cleanup after an iSCSI connection has been terminated. diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index 45db9047b..e5cb9aae2 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -39,6 +39,7 @@ from cinder import utils import cinder.volume.driver from cinder.volume.drivers.san.hp import hp_3par_common as hpcommon from cinder.volume.drivers.san import san +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -163,6 +164,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): finally: self.common.client_logout() + @fczm_utils.AddFCZone @utils.synchronized('3par', external=True) def initialize_connection(self, volume, connector): """Assigns the volume to a server. @@ -221,6 +223,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): finally: self.common.client_logout() + @fczm_utils.RemoveFCZone @utils.synchronized('3par', external=True) def terminate_connection(self, volume, connector, **kwargs): """Driver entry point to unattach a volume from an instance.""" diff --git a/cinder/volume/drivers/san/hp/hp_msa_fc.py b/cinder/volume/drivers/san/hp/hp_msa_fc.py index c86650c24..1b1657122 100644 --- a/cinder/volume/drivers/san/hp/hp_msa_fc.py +++ b/cinder/volume/drivers/san/hp/hp_msa_fc.py @@ -18,6 +18,7 @@ from cinder import utils import cinder.volume.driver from cinder.volume.drivers.san.hp import hp_msa_common as hpcommon from cinder.volume.drivers.san import san +from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -80,6 +81,7 @@ class HPMSAFCDriver(cinder.volume.driver.FibreChannelDriver): finally: self.common.client_logout() + @fczm_utils.AddFCZone @utils.synchronized('msa', external=True) def initialize_connection(self, volume, connector): self.common.client_login() @@ -97,6 +99,7 @@ class HPMSAFCDriver(cinder.volume.driver.FibreChannelDriver): finally: self.common.client_logout() + @fczm_utils.RemoveFCZone @utils.synchronized('msa', external=True) def terminate_connection(self, volume, connector, **kwargs): self.common.client_login() diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index 3e7cf8c74..617895b88 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -62,7 +62,6 @@ from cinder.volume.flows.manager import manage_existing from cinder.volume import rpcapi as volume_rpcapi from cinder.volume import utils as volume_utils from cinder.volume import volume_types -from cinder.zonemanager.fc_zone_manager import ZoneManager from eventlet.greenpool import GreenPool @@ -180,7 +179,6 @@ class VolumeManager(manager.SchedulerDependentManager): db=self.db, host=self.host) - self.zonemanager = None try: self.extra_capabilities = jsonutils.loads( self.driver.configuration.extra_capabilities) @@ -200,14 +198,6 @@ class VolumeManager(manager.SchedulerDependentManager): """ ctxt = context.get_admin_context() - if self.configuration.safe_get('zoning_mode') == 'fabric': - self.zonemanager = ZoneManager(configuration=self.configuration) - LOG.info(_("Starting FC Zone Manager %(zm_version)s," - " Driver %(drv_name)s %(drv_version)s") % - {'zm_version': self.zonemanager.get_version(), - 'drv_name': self.zonemanager.driver.__class__.__name__, - 'drv_version': self.zonemanager.driver.get_version()}) - LOG.info(_("Starting volume driver %(driver_name)s (%(version)s)") % {'driver_name': self.driver.__class__.__name__, 'version': self.driver.get_version()}) @@ -848,13 +838,7 @@ class VolumeManager(manager.SchedulerDependentManager): if volume_metadata.get('readonly') == 'True' else 'rw') conn_info['data']['access_mode'] = access_mode - # NOTE(skolathur): If volume_type is fibre_channel, invoke - # FCZoneManager to add access control via FC zoning. - vol_type = conn_info.get('driver_volume_type', None) - mode = self.configuration.zoning_mode - LOG.debug("Zoning Mode: %s", mode) - if vol_type == 'fibre_channel' and self.zonemanager: - self._add_or_delete_fc_connection(conn_info, 1) + return conn_info def terminate_connection(self, context, volume_id, connector, force=False): @@ -869,17 +853,8 @@ class VolumeManager(manager.SchedulerDependentManager): volume_ref = self.db.volume_get(context, volume_id) try: - conn_info = self.driver.terminate_connection(volume_ref, - connector, - force=force) - # NOTE(skolathur): If volume_type is fibre_channel, invoke - # FCZoneManager to remove access control via FC zoning. - if conn_info: - vol_type = conn_info.get('driver_volume_type', None) - mode = self.configuration.zoning_mode - LOG.debug("Zoning Mode: %s", mode) - if vol_type == 'fibre_channel' and self.zonemanager: - self._add_or_delete_fc_connection(conn_info, 0) + self.driver.terminate_connection(volume_ref, connector, + force=force) except Exception as err: err_msg = (_('Unable to terminate volume connection: %(err)s') % {'err': err}) @@ -1336,34 +1311,3 @@ class VolumeManager(manager.SchedulerDependentManager): # Update volume stats self.stats['allocated_capacity_gb'] += volume_ref['size'] return volume_ref['id'] - - def _add_or_delete_fc_connection(self, conn_info, zone_op): - """Add or delete connection control to fibre channel network. - - In case of fibre channel, when zoning mode is set as fabric - ZoneManager is invoked to apply FC zoning configuration to the network - using initiator and target WWNs used for attach/detach. - - params conn_info: connector passed by volume driver after - initialize_connection or terminate_connection. - params zone_op: Indicates if it is a zone add or delete operation - zone_op=0 for delete connection and 1 for add connection - """ - _initiator_target_map = None - if 'initiator_target_map' in conn_info['data']: - _initiator_target_map = conn_info['data']['initiator_target_map'] - LOG.debug("Initiator Target map:%s", _initiator_target_map) - # NOTE(skolathur): Invoke Zonemanager to handle automated FC zone - # management when vol_type is fibre_channel and zoning_mode is fabric - # Initiator_target map associating each initiator WWN to one or more - # target WWN is passed to ZoneManager to add or update zone config. - LOG.debug("Zoning op: %s", zone_op) - if _initiator_target_map is not None: - try: - if zone_op == 1: - self.zonemanager.add_connection(_initiator_target_map) - elif zone_op == 0: - self.zonemanager.delete_connection(_initiator_target_map) - except exception.ZoneManagerException as e: - with excutils.save_and_reraise_exception(): - LOG.error(e) diff --git a/cinder/zonemanager/fc_zone_manager.py b/cinder/zonemanager/fc_zone_manager.py index 2eb900227..1710d2219 100644 --- a/cinder/zonemanager/fc_zone_manager.py +++ b/cinder/zonemanager/fc_zone_manager.py @@ -65,12 +65,23 @@ CONF.register_opts(zone_manager_opts, 'fc-zone-manager') class ZoneManager(fc_common.FCCommon): - """Manages Connection control during attach/detach.""" + """Manages Connection control during attach/detach. - VERSION = "1.0" + Version History: + 1.0 - Initial version + 1.0.1 - Added __new__ for singleton + + """ + + VERSION = "1.0.1" driver = None fabric_names = [] + def __new__(class_, *args, **kwargs): + if not hasattr(class_, "_instance"): + class_._instance = object.__new__(class_, *args, **kwargs) + return class_._instance + def __init__(self, **kwargs): """Load the driver from the one specified in args, or from flags.""" super(ZoneManager, self).__init__(**kwargs) @@ -79,6 +90,9 @@ class ZoneManager(fc_common.FCCommon): if self.configuration: self.configuration.append_config_values(zone_manager_opts) + self._build_driver() + + def _build_driver(self): zone_driver = self.configuration.zone_driver LOG.debug("Zone Driver from config: {%s}", zone_driver) diff --git a/cinder/zonemanager/utils.py b/cinder/zonemanager/utils.py new file mode 100644 index 000000000..fd976f7cd --- /dev/null +++ b/cinder/zonemanager/utils.py @@ -0,0 +1,96 @@ +# (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +""" +Utility functions related to the Zone Manager. + +""" +import logging + +from cinder.openstack.common import log +from cinder.volume.configuration import Configuration +from cinder.volume import manager +from cinder.zonemanager import fc_zone_manager + +LOG = log.getLogger(__name__) +LOG.logger.setLevel(logging.DEBUG) + + +def create_zone_manager(): + """If zoning is enabled, build the Zone Manager.""" + config = Configuration(manager.volume_manager_opts) + LOG.debug("zoning mode %s" % config.safe_get('zoning_mode')) + if config.safe_get('zoning_mode') == 'fabric': + LOG.debug("FC Zone Manager enabled.") + zm = fc_zone_manager.ZoneManager(configuration=config) + LOG.info(_("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 not enabled in cinder.conf.") + return None + + +def AddFCZone(initialize_connection): + """Decorator to add a FC Zone.""" + def decorator(self, *args, **kwargs): + conn_info = initialize_connection(self, *args, **kwargs) + if not conn_info: + LOG.warn(_("Driver didn't return connection info, " + "can't add zone.")) + return None + + vol_type = conn_info.get('driver_volume_type', None) + if vol_type == 'fibre_channel': + + if 'initiator_target_map' in conn_info['data']: + init_target_map = conn_info['data']['initiator_target_map'] + zm = create_zone_manager() + if zm: + LOG.debug("Add FC Zone for mapping '%s'." % + init_target_map) + zm.add_connection(init_target_map) + + return conn_info + + return decorator + + +def RemoveFCZone(terminate_connection): + """Decorator for FC drivers to remove zone.""" + def decorator(self, *args, **kwargs): + conn_info = terminate_connection(self, *args, **kwargs) + if not conn_info: + LOG.warn(_("Driver didn't return connection info from " + "terminate_connection call.")) + return None + + vol_type = conn_info.get('driver_volume_type', None) + if vol_type == 'fibre_channel': + + if 'initiator_target_map' in conn_info['data']: + init_target_map = conn_info['data']['initiator_target_map'] + zm = create_zone_manager() + if zm: + LOG.debug("Remove FC Zone for mapping '%s'." % + init_target_map) + zm.delete_connection(init_target_map) + + return conn_info + + return decorator -- 2.45.2