]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fixing 3PAR excessive FC port usage
authorAnthony Lee <anthony.mic.lee@hp.com>
Thu, 21 Aug 2014 22:57:06 +0000 (15:57 -0700)
committerAnthony Lee <anthony.mic.lee@hp.com>
Tue, 26 Aug 2014 18:57:55 +0000 (11:57 -0700)
Updating the 3PAR FC driver so that it can detect if there is
only a single FC path available.  When a single FC path is
detected only a single VLUN will be created instead of one for
every available NSP on the host.  This will prevent a host
from using extra FC ports that are not needed.  If multiple
FC paths are available all the ports will still be used.

Closes-Bug: 1360001
Change-Id: I4b2bdf12289dd255dd959675991fa9e2e0fcbd1e

cinder/tests/test_hp3par.py
cinder/volume/drivers/san/hp/hp_3par_fc.py
cinder/zonemanager/utils.py

index 8a159ea8825a038529c3add9a54c002b96214a48..a5b17227967ad2b5a225a167a4dec05e72e5c889 100644 (file)
@@ -1954,18 +1954,97 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
             mock.call.getHost(self.FAKE_HOST),
             mock.ANY,
             mock.call.getHost(self.FAKE_HOST),
+            mock.call.getPorts(),
             mock.call.createVLUN(
                 self.VOLUME_3PAR_NAME,
                 auto=True,
                 hostname=self.FAKE_HOST),
             mock.call.getHostVLUNs(self.FAKE_HOST),
-            mock.call.getPorts(),
             mock.call.logout()]
 
         mock_client.assert_has_calls(expected)
 
         self.assertDictMatch(result, self.properties)
 
+    @mock.patch('cinder.zonemanager.utils.create_lookup_service')
+    def test_initialize_connection_with_lookup_single_nsp(self, mock_lookup):
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        class fake_lookup_object:
+            def get_device_mapping_from_network(self, connector, target_wwns):
+                fake_map = {
+                    'FAB_1': {
+                        'target_port_wwn_list': ['0987654321234'],
+                        'initiator_port_wwn_list': ['123456789012345']
+                    }
+                }
+                return fake_map
+        mock_lookup.return_value = fake_lookup_object()
+        mock_client = self.setup_driver()
+        mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG}
+        mock_client.getCPG.return_value = {}
+        mock_client.getHost.side_effect = [
+            hpexceptions.HTTPNotFound('fake'),
+            {'name': self.FAKE_HOST,
+                'FCPaths': [{'driverVersion': None,
+                             'firmwareVersion': None,
+                             'hostSpeed': 0,
+                             'model': None,
+                             'portPos': {'cardPort': 1, 'node': 1,
+                                         'slot': 2},
+                             'vendor': None,
+                             'wwn': self.wwn[0]}]}]
+        mock_client.findHost.return_value = self.FAKE_HOST
+        mock_client.getHostVLUNs.return_value = [
+            {'active': True,
+             'volumeName': self.VOLUME_3PAR_NAME,
+             'lun': 90, 'type': 0}]
+        location = ("%(volume_name)s,%(lun_id)s,%(host)s,%(nsp)s" %
+                    {'volume_name': self.VOLUME_3PAR_NAME,
+                     'lun_id': 90,
+                     'host': self.FAKE_HOST,
+                     'nsp': 'something'})
+        mock_client.createVLUN.return_value = location
+
+        connector = {'ip': '10.0.0.2',
+                     'initiator': 'iqn.1993-08.org.debian:01:222',
+                     'wwpns': [self.wwn[0]],
+                     'wwnns': ["223456789012345"],
+                     'host': self.FAKE_HOST}
+
+        expected_properties = {
+            'driver_volume_type': 'fibre_channel',
+            'data': {
+                'target_lun': 90,
+                'target_wwn': ['0987654321234'],
+                'target_discovered': True,
+                'initiator_target_map': {'123456789012345':
+                                         ['0987654321234']
+                                         }}}
+
+        result = self.driver.initialize_connection(self.volume, connector)
+
+        expected = [
+            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.getVolume(self.VOLUME_3PAR_NAME),
+            mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getHost(self.FAKE_HOST),
+            mock.ANY,
+            mock.call.getHost(self.FAKE_HOST),
+            mock.call.getPorts(),
+            mock.call.getPorts(),
+            mock.call.createVLUN(
+                self.VOLUME_3PAR_NAME,
+                auto=True,
+                hostname=self.FAKE_HOST,
+                portPos={'node': 7, 'slot': 1, 'cardPort': 1}),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
+            mock.call.logout()]
+
+        mock_client.assert_has_calls(expected)
+
+        self.assertDictMatch(result, expected_properties)
+
     def test_terminate_connection(self):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
@@ -2017,6 +2096,68 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
                                                      self.connector)
         mock_client.assert_has_calls(expected)
 
+    @mock.patch('cinder.zonemanager.utils.create_lookup_service')
+    def test_terminate_connection_with_lookup(self, mock_lookup):
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        class fake_lookup_object:
+            def get_device_mapping_from_network(self, connector, target_wwns):
+                fake_map = {
+                    'FAB_1': {
+                        'target_port_wwn_list': ['0987654321234'],
+                        'initiator_port_wwn_list': ['123456789012345']
+                    }
+                }
+                return fake_map
+        mock_lookup.return_value = fake_lookup_object()
+        mock_client = self.setup_driver()
+
+        effects = [
+            [{'active': True, 'volumeName': self.VOLUME_3PAR_NAME,
+              'lun': None, 'type': 0}],
+            hpexceptions.HTTPNotFound]
+
+        mock_client.getHostVLUNs.side_effect = effects
+
+        expected = [
+            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
+            mock.call.deleteVLUN(
+                self.VOLUME_3PAR_NAME,
+                None,
+                self.FAKE_HOST),
+            mock.call.deleteHost(self.FAKE_HOST),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
+            mock.call.getPorts(),
+            mock.call.logout()]
+
+        conn_info = self.driver.terminate_connection(self.volume,
+                                                     self.connector)
+        mock_client.assert_has_calls(expected)
+        self.assertIn('data', conn_info)
+        self.assertIn('initiator_target_map', conn_info['data'])
+        mock_client.reset_mock()
+
+        mock_client.getHostVLUNs.side_effect = effects
+
+        # mock some deleteHost exceptions that are handled
+        delete_with_vlun = hpexceptions.HTTPConflict(
+            error={'message': "has exported VLUN"})
+        delete_with_hostset = hpexceptions.HTTPConflict(
+            error={'message': "host is a member of a set"})
+        mock_client.deleteHost = mock.Mock(
+            side_effect=[delete_with_vlun, delete_with_hostset])
+
+        conn_info = self.driver.terminate_connection(self.volume,
+                                                     self.connector)
+        mock_client.assert_has_calls(expected)
+        mock_client.reset_mock()
+        mock_client.getHostVLUNs.side_effect = effects
+
+        conn_info = self.driver.terminate_connection(self.volume,
+                                                     self.connector)
+        mock_client.assert_has_calls(expected)
+
     def test_terminate_connection_more_vols(self):
         mock_client = self.setup_driver()
         # mock more than one vlun on the host (don't even try to remove host)
index 6bc6e746a3b8d440daa2b77e478f2bc155b046f2..936722a172bda874138412975f4d81fd66a02fd1 100644 (file)
@@ -66,16 +66,19 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
         2.0.4 - Added support for managing/unmanaging of volumes
         2.0.5 - Only remove FC Zone on last volume detach
         2.0.6 - Added support for volume retype
+        2.0.7 - Only one FC port is used when a single FC path
+                is present.  bug #1360001
 
     """
 
-    VERSION = "2.0.6"
+    VERSION = "2.0.7"
 
     def __init__(self, *args, **kwargs):
         super(HP3PARFCDriver, self).__init__(*args, **kwargs)
         self.common = None
         self.configuration.append_config_values(hpcommon.hp3par_opts)
         self.configuration.append_config_values(san.san_opts)
+        self.lookup_service = fczm_utils.create_lookup_service()
 
     def _init_common(self):
         return hpcommon.HP3PARCommon(self.configuration)
@@ -210,11 +213,20 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
             # we have to make sure we have a host
             host = self._create_host(volume, connector)
 
-            # now that we have a host, create the VLUN
-            vlun = self.common.create_vlun(volume, host)
+            target_wwns, init_targ_map, numPaths = \
+                self._build_initiator_target_map(connector)
 
-            target_wwns, init_targ_map = self._build_initiator_target_map(
-                connector)
+            # now that we have a host, create the VLUN
+            if self.lookup_service is not None and numPaths == 1:
+                nsp = None
+                active_fc_port_list = self.common.get_active_fc_target_ports()
+                for port in active_fc_port_list:
+                    if port['portWWN'].lower() == target_wwns[0].lower():
+                        nsp = port['nsp']
+                        break
+                vlun = self.common.create_vlun(volume, host, nsp)
+            else:
+                vlun = self.common.create_vlun(volume, host)
 
             info = {'driver_volume_type': 'fibre_channel',
                     'data': {'target_lun': vlun['lun'],
@@ -244,8 +256,9 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
                 # No more exports for this host.
                 LOG.info(_("Need to remove FC Zone, building initiator "
                          "target map"))
-                target_wwns, init_targ_map = self._build_initiator_target_map(
-                    connector)
+
+                target_wwns, init_targ_map, numPaths = \
+                    self._build_initiator_target_map(connector)
 
                 info['data'] = {'target_wwn': target_wwns,
                                 'initiator_target_map': init_targ_map}
@@ -258,18 +271,41 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
         """Build the target_wwns and the initiator target map."""
 
         fc_ports = self.common.get_active_fc_target_ports()
+        all_target_wwns = []
         target_wwns = []
+        init_targ_map = {}
+        numPaths = 0
 
         for port in fc_ports:
-            target_wwns.append(port['portWWN'])
-
-        initiator_wwns = connector['wwpns']
+            all_target_wwns.append(port['portWWN'])
+
+        if self.lookup_service is not None:
+            # use FC san lookup to determine which NSPs to use
+            # for the new VLUN.
+            dev_map = self.lookup_service.get_device_mapping_from_network(
+                connector['wwpns'],
+                all_target_wwns)
+
+            for fabric_name in dev_map:
+                fabric = dev_map[fabric_name]
+                target_wwns += fabric['target_port_wwn_list']
+                for initiator in fabric['initiator_port_wwn_list']:
+                    if initiator not in init_targ_map:
+                        init_targ_map[initiator] = []
+                    init_targ_map[initiator] += fabric['target_port_wwn_list']
+                    init_targ_map[initiator] = list(set(
+                        init_targ_map[initiator]))
+                    for target in init_targ_map[initiator]:
+                        numPaths += 1
+            target_wwns = list(set(target_wwns))
+        else:
+            initiator_wwns = connector['wwpns']
+            target_wwns = all_target_wwns
 
-        init_targ_map = {}
-        for initiator in initiator_wwns:
-            init_targ_map[initiator] = target_wwns
+            for initiator in initiator_wwns:
+                init_targ_map[initiator] = target_wwns
 
-        return target_wwns, init_targ_map
+        return target_wwns, init_targ_map, numPaths
 
     def _create_3par_fibrechan_host(self, hostname, wwns, domain, persona_id):
         """Create a 3PAR host.
index f9941592c00004fd8f613fde7248e0104695b776..13c91e3f8c3e90be69fdc3120cc8a5c9141cd19e 100644 (file)
@@ -23,6 +23,7 @@ from cinder.i18n import _
 from cinder.openstack.common import log
 from cinder.volume.configuration import Configuration
 from cinder.volume import manager
+from cinder.zonemanager import fc_san_lookup_service
 from cinder.zonemanager import fc_zone_manager
 
 LOG = log.getLogger(__name__)
@@ -47,6 +48,19 @@ def create_zone_manager():
         return None
 
 
+def create_lookup_service():
+    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 Lookup Service enabled.")
+        lookup = fc_san_lookup_service.FCSanLookupService(configuration=config)
+        LOG.info(_("Using FC lookup service %s") % lookup.lookup_service)
+        return lookup
+    else:
+        LOG.debug("FC Lookup Service not enabled in cinder.conf.")
+        return None
+
+
 def AddFCZone(initialize_connection):
     """Decorator to add a FC Zone."""
     def decorator(self, *args, **kwargs):