]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Ensure FC ZoneManager is called
authorWalter A. Boring IV <walter.boring@hp.com>
Thu, 19 Jun 2014 17:35:29 +0000 (10:35 -0700)
committerWalter A. Boring IV <walter.boring@hp.com>
Mon, 7 Jul 2014 23:19:03 +0000 (16:19 -0700)
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

14 files changed:
cinder/tests/fake_driver.py
cinder/tests/test_volume.py
cinder/tests/zonemanager/test_fc_zone_manager.py
cinder/tests/zonemanager/test_volume_driver.py [new file with mode: 0644]
cinder/tests/zonemanager/test_volume_manager_fc.py [deleted file]
cinder/volume/drivers/emc/emc_smis_fc.py
cinder/volume/drivers/huawei/huawei_hvs.py
cinder/volume/drivers/huawei/huawei_t.py
cinder/volume/drivers/ibm/storwize_svc/__init__.py
cinder/volume/drivers/san/hp/hp_3par_fc.py
cinder/volume/drivers/san/hp/hp_msa_fc.py
cinder/volume/manager.py
cinder/zonemanager/fc_zone_manager.py
cinder/zonemanager/utils.py [new file with mode: 0644]

index 72f713689f3d67bb253f84a7fc2315bd0fc26412..735608bd8cfe73bcd73b67642c8ccf9a0424e07f 100644 (file)
@@ -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."""
 
index f22ac1f167e3362f609560514c15a9bb9fe9dd49..c7b44b1d23e356657a95acbc57b28c6f68a54e05 100644 (file)
@@ -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):
index 909e800690464f65ee8f26dd727283877f94a58f..656630f5079c23172788ad43afd7f4796d237744 100644 (file)
@@ -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 (file)
index 0000000..6f97518
--- /dev/null
@@ -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 (file)
index 5cc5a70..0000000
+++ /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
index e15f2b140c21d15d36ac8e953b2d9e586481f644..cd39777bf94d8f9d3c88ea7ac6ede21b66e42cb0 100644 (file)
@@ -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)
index afaf2ca728fccc538d5b4a8dcc1b9e470a49ed4b..098346561641a662ad8c2f14c0f41aa1eeaa8de3 100644 (file)
@@ -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)
index 9152afd75b57569e54b61bae31641043ac818ec4..b88e5e90a17b0e9613b2a4e59b53ec3e75533407 100644 (file)
@@ -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, '
index 471ddad3363ef153d7a7cac736a631dbd1ea3afd..9a7230d77990ebb28befdfaff36b10380364a6da 100644 (file)
@@ -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.
index 45db9047b8611e2ed054f839dc5e55a31a3219f6..e5cb9aae2cda89b1ec39fd4ab83833df158d1147 100644 (file)
@@ -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."""
index c86650c24877c1bddbee6fc5e8bd9c447893856e..1b16571224993a6affb5d38ee18c84bf319017c2 100644 (file)
@@ -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()
index 3e7cf8c741a3acc3ca77840e42eef952f631c2a6..617895b8889698d2c37035bec80b0eae7bc2b8b2 100644 (file)
@@ -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)
index 2eb900227b6bbda0f84365723e14d055eafcc7b6..1710d22195e5d00a54382a8c14dac65887536bc9 100644 (file)
@@ -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 (file)
index 0000000..fd976f7
--- /dev/null
@@ -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