]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add Fibre Channel drivers for Huawei storage systems
authorzhangchao010 <zhangchao010@huawei.com>
Sat, 31 Aug 2013 02:24:43 +0000 (10:24 +0800)
committerGerrit Code Review <review@openstack.org>
Tue, 3 Sep 2013 02:54:05 +0000 (02:54 +0000)
This is the third patch, changes as follows:
1.Add Fibre Channel drivers for huawei OceanStor T series and Dorado
series arrays. Dorado FC driver inherits codes from FC driver of T.
The FC drivers call module ssh_common which has been defined in
the preview patch: https://review.openstack.org/#/c/41721/
2.Add unit test for the changes.

Implements: blueprint huawei-fibre-channel-volume-driver
Change-Id: Iee20d9746004b57777a7161827b4a23cb10f0859

cinder/tests/test_huawei_t_dorado.py
cinder/volume/drivers/huawei/__init__.py
cinder/volume/drivers/huawei/huawei_dorado.py
cinder/volume/drivers/huawei/huawei_t.py

index 6903b615ba2c0571ec24c03a9c0c5e5b5fffa8f7..43442e6821b2e0b086acbc67db25a46876a6432d 100644 (file)
@@ -125,7 +125,8 @@ INITIATOR_SETTING = {'TargetIQN': 'iqn.2006-08.com.huawei:oceanspace:2103037:',
                      'TargetIQN-form': 'iqn.2006-08.com.huawei:oceanspace:'
                      '2103037::1020001:192.168.100.2',
                      'Initiator Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
-                     'Initiator TargetIP': '192.168.100.2'}
+                     'Initiator TargetIP': '192.168.100.2',
+                     'WWN': ['2011666666666565']}
 
 FAKE_VOLUME = {'name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
                'id': 'lele34fe-223f-dd33-4423-asdfghjklqwe',
@@ -147,6 +148,8 @@ FAKE_SNAPSHOT = {'name': 'keke34fe-223f-dd33-4423-asdfghjklqwf',
                  'provider_location': None}
 
 FAKE_CONNECTOR = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
+                  'wwpns': ['1000000164s45126'],
+                  'wwnns': ['2000666666666565'],
                   'host': 'fakehost'}
 
 RESPOOL_A_SIM = {'Size': '10240', 'Valid Size': '5120'}
@@ -248,8 +251,14 @@ class FakeChannel():
             out = self.simu.cli_addhostmap(params)
         elif cmd == 'delhostmap':
             out = self.simu.cli_delhostmap(params)
+        elif cmd == 'showfreeport':
+            out = self.simu.cli_showfreeport(params)
+        elif cmd == 'showhostpath':
+            out = self.simu.cli_showhostpath(params)
         elif cmd == 'chglun':
             out = self.simu.cli_chglun(params)
+        elif cmd == 'showfcmode':
+            out = self.simu.cli_showfcmode(params)
         out = self.command[:-1] + out + '\nadmin:/>'
         return out.replace('\n', '\r\n')
 
@@ -825,6 +834,51 @@ Multipath Type
             out = 'command operates successfully'
         return out
 
+    def cli_showfreeport(self, params):
+        out = """/>showfreeport
+=======================================================================
+                      Host Free Port Information
+-----------------------------------------------------------------------
+  WWN Or MAC          Type    Location              Connection Status
+-----------------------------------------------------------------------
+  1000000164s45126    FC      Primary Controller    Connected
+=======================================================================
+"""
+        HOST_PORT_INFO['ID'] = '2'
+        HOST_PORT_INFO['Name'] = 'FCInitiator001'
+        HOST_PORT_INFO['Info'] = '1000000164s45126'
+        HOST_PORT_INFO['Type'] = 'FC'
+        return out
+
+    def cli_showhostpath(self, params):
+        host = params[params.index('-host') + 1]
+        out = """/>showhostpath -host 1
+=======================================
+        Multi Path Information
+---------------------------------------
+  Host ID           | %s
+  Controller ID     | B
+  Port Type         | FC
+  Initiator WWN     | 1000000164s45126
+  Target WWN        | %s
+  Host Port ID      | 0
+  Link Status       | Normal
+=======================================
+""" % (host, INITIATOR_SETTING['WWN'][0])
+        return out
+
+    def cli_showfcmode(self, params):
+        out = """/>showfcport
+=========================================================================
+                      FC Port Topology Mode
+-------------------------------------------------------------------------
+  Controller ID   Interface Module ID   Port ID   WWN    Current Mode
+-------------------------------------------------------------------------
+  B               1                     P0        %s     --
+=========================================================================
+-""" % INITIATOR_SETTING['WWN'][0]
+        return out
+
     def cli_chglun(self, params):
         if params[params.index('-lun') + 1] == VOLUME_SNAP_ID['vol']:
             LUN_INFO['Owner Controller'] = 'B'
@@ -1393,6 +1447,155 @@ class HuaweiTISCSIDriverTestCase(test.TestCase):
         self.assertEqual(stats['storage_protocol'], 'iSCSI')
 
 
+class HuaweiTFCDriverTestCase(test.TestCase):
+    def __init__(self, *args, **kwargs):
+        super(HuaweiTFCDriverTestCase, self).__init__(*args, **kwargs)
+
+    def setUp(self):
+        super(HuaweiTFCDriverTestCase, self).setUp()
+
+        self.tmp_dir = tempfile.mkdtemp()
+        self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+        create_fake_conf_file(self.fake_conf_file)
+        modify_conf(self.fake_conf_file, 'Storage/Protocol', 'FC')
+        self.configuration = mox.MockObject(conf.Configuration)
+        self.configuration.cinder_huawei_conf_file = self.fake_conf_file
+        self.configuration.append_config_values(mox.IgnoreArg())
+
+        self.stubs.Set(time, 'sleep', Fake_sleep)
+        self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
+        self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
+                       Fake_change_file_mode)
+        self._init_driver()
+
+    def _init_driver(self):
+        Curr_test[0] = 'T'
+        self.driver = HuaweiVolumeDriver(configuration=self.configuration)
+        self.driver.do_setup(None)
+
+    def tearDown(self):
+        if os.path.exists(self.fake_conf_file):
+            os.remove(self.fake_conf_file)
+        shutil.rmtree(self.tmp_dir)
+        super(HuaweiTFCDriverTestCase, self).tearDown()
+
+    def test_validate_connector_failed(self):
+        invalid_connector = {'host': 'testhost'}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.validate_connector,
+                          invalid_connector)
+
+    def test_create_delete_volume(self):
+        self.driver.create_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['ID'], None)
+
+    def test_create_delete_snapshot(self):
+        self.driver.create_volume(FAKE_VOLUME)
+        self.driver.create_snapshot(FAKE_SNAPSHOT)
+        self.assertEqual(SNAPSHOT_INFO['ID'], VOLUME_SNAP_ID['snap'])
+        self.driver.delete_snapshot(FAKE_SNAPSHOT)
+        self.assertEqual(SNAPSHOT_INFO['ID'], None)
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['ID'], None)
+
+    def test_create_cloned_volume(self):
+        self.driver.create_volume(FAKE_VOLUME)
+        ret = self.driver.create_cloned_volume(FAKE_CLONED_VOLUME, FAKE_VOLUME)
+        self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+        self.assertEqual(ret['provider_location'], CLONED_LUN_INFO['ID'])
+        self.driver.delete_volume(FAKE_CLONED_VOLUME)
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(CLONED_LUN_INFO['ID'], None)
+        self.assertEqual(LUN_INFO['ID'], None)
+
+    def test_create_snapshot_volume(self):
+        self.driver.create_volume(FAKE_VOLUME)
+        self.driver.create_snapshot(FAKE_SNAPSHOT)
+        ret = self.driver.create_volume_from_snapshot(FAKE_CLONED_VOLUME,
+                                                      FAKE_SNAPSHOT)
+        self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+        self.assertEqual(ret['provider_location'], CLONED_LUN_INFO['ID'])
+        self.driver.delete_volume(FAKE_CLONED_VOLUME)
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(CLONED_LUN_INFO['ID'], None)
+        self.assertEqual(LUN_INFO['ID'], None)
+
+    def test_initialize_terminitat_connection(self):
+        self.driver.create_volume(FAKE_VOLUME)
+        ret = self.driver.initialize_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+        fc_properties = ret['data']
+        self.assertEquals(fc_properties['target_wwn'],
+                          INITIATOR_SETTING['WWN'])
+        self.assertEqual(MAP_INFO["DEV LUN ID"], LUN_INFO['ID'])
+
+        self.driver.terminate_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+        self.assertEqual(MAP_INFO["DEV LUN ID"], None)
+        self.assertEqual(MAP_INFO["Host LUN ID"], None)
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['ID'], None)
+
+    def _test_get_volume_stats(self):
+        stats = self.driver.get_volume_stats(True)
+        fakecapacity = float(POOL_SETTING['Free Capacity']) / 1024
+        self.assertEqual(stats['free_capacity_gb'], fakecapacity)
+        self.assertEqual(stats['storage_protocol'], 'FC')
+
+
+class HuaweiDorado5100FCDriverTestCase(HuaweiTFCDriverTestCase):
+    def __init__(self, *args, **kwargs):
+        super(HuaweiDorado5100FCDriverTestCase, self).__init__(*args, **kwargs)
+
+    def setUp(self):
+        super(HuaweiDorado5100FCDriverTestCase, self).setUp()
+
+    def _init_driver(self):
+        Curr_test[0] = 'Dorado5100'
+        modify_conf(self.fake_conf_file, 'Storage/Product', 'Dorado')
+        self.driver = HuaweiVolumeDriver(configuration=self.configuration)
+        self.driver.do_setup(None)
+
+    def test_create_cloned_volume(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_cloned_volume,
+                          FAKE_CLONED_VOLUME, FAKE_VOLUME)
+
+    def test_create_snapshot_volume(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          FAKE_CLONED_VOLUME, FAKE_SNAPSHOT)
+
+
+class HuaweiDorado2100G2FCDriverTestCase(HuaweiTFCDriverTestCase):
+    def __init__(self, *args, **kwargs):
+        super(HuaweiDorado2100G2FCDriverTestCase, self).__init__(*args,
+                                                                 **kwargs)
+
+    def setUp(self):
+        super(HuaweiDorado2100G2FCDriverTestCase, self).setUp()
+
+    def _init_driver(self):
+        Curr_test[0] = 'Dorado2100G2'
+        modify_conf(self.fake_conf_file, 'Storage/Product', 'Dorado')
+        self.driver = HuaweiVolumeDriver(configuration=self.configuration)
+        self.driver.do_setup(None)
+
+    def test_create_cloned_volume(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_cloned_volume,
+                          FAKE_CLONED_VOLUME, FAKE_VOLUME)
+
+    def test_create_delete_snapshot(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_snapshot, FAKE_SNAPSHOT)
+
+    def test_create_snapshot_volume(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          FAKE_CLONED_VOLUME, FAKE_SNAPSHOT)
+
+
 class HuaweiDorado5100ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase):
     def __init__(self, *args, **kwargs):
         super(HuaweiDorado5100ISCSIDriverTestCase, self).__init__(*args,
index 732db1d753d6842b1385eccbd2973edd9efc36c0..f1f5777bf8e9b9fd23cb14ff0f6a0caaedd67655 100644 (file)
@@ -48,7 +48,7 @@ class HuaweiVolumeDriver(object):
     def __init__(self, *args, **kwargs):
         super(HuaweiVolumeDriver, self).__init__()
         self._product = {'T': huawei_t, 'Dorado': huawei_dorado}
-        self._protocol = {'iSCSI': 'ISCSIDriver'}
+        self._protocol = {'iSCSI': 'ISCSIDriver', 'FC': 'FCDriver'}
 
         self.driver = self._instantiate_driver(*args, **kwargs)
 
@@ -85,7 +85,7 @@ class HuaweiVolumeDriver(object):
         else:
             msg = (_('"Product" or "Protocol" is illegal. "Product" should '
                      'be set to either T or Dorado. "Protocol" should be set '
-                     'to iSCSI. Product: %(product)s '
+                     'to either iSCSI or FC. Product: %(product)s '
                      'Protocol: %(protocol)s')
                    % {'product': str(product),
                       'protocol': str(protocol)})
index 2b1393ef2d25306b428de8f31ef6ea1006c15896..469c95252e2e437bb02e916d1da033901aab00c0 100644 (file)
 Volume Drivers for Huawei OceanStor Dorado series storage arrays.
 """
 
+import re
+
+from cinder.openstack.common import log as logging
 from cinder.volume.drivers.huawei import huawei_t
 from cinder.volume.drivers.huawei import ssh_common
 
+LOG = logging.getLogger(__name__)
+
 
 class HuaweiDoradoISCSIDriver(huawei_t.HuaweiTISCSIDriver):
     """ISCSI driver class for Huawei OceanStor Dorado storage arrays."""
@@ -36,3 +41,68 @@ class HuaweiDoradoISCSIDriver(huawei_t.HuaweiTISCSIDriver):
         self.common.do_setup(context)
         self._assert_cli_out = self.common._assert_cli_out
         self._assert_cli_operate_out = self.common._assert_cli_operate_out
+
+
+class HuaweiDoradoFCDriver(huawei_t.HuaweiTFCDriver):
+    """FC driver class for Huawei OceanStor Dorado storage arrays."""
+
+    def __init__(self, *args, **kwargs):
+        super(HuaweiDoradoFCDriver, self).__init__(*args, **kwargs)
+
+    def do_setup(self, context):
+        """Instantiate common class."""
+        self.common = ssh_common.DoradoCommon(configuration=self.configuration)
+
+        self.common.do_setup(context)
+        self._assert_cli_out = self.common._assert_cli_out
+        self._assert_cli_operate_out = self.common._assert_cli_operate_out
+
+    def _get_host_port_details(self, hostid):
+        cli_cmd = 'showfcmode'
+        out = self.common._execute_cli(cli_cmd)
+
+        self._assert_cli_out(re.search('FC Port Topology Mode', out),
+                             '_get_tgt_fc_port_wwns',
+                             'Failed to get FC port WWNs.',
+                             cli_cmd, out)
+
+        return [line.split()[3] for line in out.split('\r\n')[6:-2]]
+
+    def _get_tgt_fc_port_wwns(self, port_details):
+        return port_details
+
+    def initialize_connection(self, volume, connector):
+        """Create FC connection between a volume and a host."""
+        LOG.debug(_('initialize_connection: volume name: %(vol)s '
+                    'host: %(host)s initiator: %(wwn)s')
+                  % {'vol': volume['name'],
+                     'host': connector['host'],
+                     'wwn': connector['wwpns']})
+
+        self.common._update_login_info()
+        # First, add a host if it is not added before.
+        host_id = self.common.add_host(connector['host'])
+        # Then, add free FC ports to the host.
+        ini_wwns = connector['wwpns']
+        free_wwns = self._get_connected_free_wwns()
+        for wwn in free_wwns:
+            if wwn in ini_wwns:
+                self._add_fc_port_to_host(host_id, wwn)
+        fc_port_details = self._get_host_port_details(host_id)
+        tgt_wwns = self._get_tgt_fc_port_wwns(fc_port_details)
+
+        LOG.debug(_('initialize_connection: Target FC ports WWNS: %s')
+                  % tgt_wwns)
+
+        # Finally, map the volume to the host.
+        volume_id = volume['provider_location']
+        hostlun_id = self.common.map_volume(host_id, volume_id)
+
+        properties = {}
+        properties['target_discovered'] = False
+        properties['target_wwn'] = tgt_wwns
+        properties['target_lun'] = int(hostlun_id)
+        properties['volume_id'] = volume['id']
+
+        return {'driver_volume_type': 'fibre_channel',
+                'data': properties}
index ed5dbc3dd220f4ea475a6eff9102d23237da2167..911727bffab1f17f70231e636240a0b198289ec9 100644 (file)
@@ -359,3 +359,225 @@ class HuaweiTISCSIDriver(driver.ISCSIDriver):
         self._stats['volume_backend_name'] = (backend_name or
                                               self.__class__.__name__)
         return self._stats
+
+
+class HuaweiTFCDriver(driver.FibreChannelDriver):
+    """FC driver for Huawei OceanStor T series storage arrays."""
+
+    VERSION = '1.0.0'
+
+    def __init__(self, *args, **kwargs):
+        super(HuaweiTFCDriver, self).__init__(*args, **kwargs)
+
+    def do_setup(self, context):
+        """Instantiate common class."""
+        self.common = ssh_common.TseriesCommon(configuration=
+                                               self.configuration)
+        self.common.do_setup(context)
+        self._assert_cli_out = self.common._assert_cli_out
+        self._assert_cli_operate_out = self.common._assert_cli_operate_out
+
+    def check_for_setup_error(self):
+        """Check something while starting."""
+        self.common.check_for_setup_error()
+
+    def create_volume(self, volume):
+        """Create a new volume."""
+        volume_id = self.common.create_volume(volume)
+        return {'provider_location': volume_id}
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a volume from a snapshot."""
+        volume_id = self.common.create_volume_from_snapshot(volume, snapshot)
+        return {'provider_location': volume_id}
+
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a clone of the specified volume."""
+        volume_id = self.common.create_cloned_volume(volume, src_vref)
+        return {'provider_location': volume_id}
+
+    def delete_volume(self, volume):
+        """Delete a volume."""
+        self.common.delete_volume(volume)
+
+    def create_export(self, context, volume):
+        """Export the volume."""
+        pass
+
+    def ensure_export(self, context, volume):
+        """Synchronously recreate an export for a volume."""
+        pass
+
+    def remove_export(self, context, volume):
+        """Remove an export for a volume."""
+        pass
+
+    def create_snapshot(self, snapshot):
+        """Create a snapshot."""
+        snapshot_id = self.common.create_snapshot(snapshot)
+        return {'provider_location': snapshot_id}
+
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot."""
+        self.common.delete_snapshot(snapshot)
+
+    def validate_connector(self, connector):
+        """Check for wwpns in connector."""
+        if 'wwpns' not in connector:
+            err_msg = (_('validate_connector: The FC driver requires the'
+                         'wwpns in the connector.'))
+            LOG.error(err_msg)
+            raise exception.VolumeBackendAPIException(data=err_msg)
+
+    def initialize_connection(self, volume, connector):
+        """Create FC connection between a volume and a host."""
+        LOG.debug(_('initialize_connection: volume name: %(vol)s '
+                    'host: %(host)s initiator: %(wwn)s')
+                  % {'vol': volume['name'],
+                     'host': connector['host'],
+                     'wwn': connector['wwpns']})
+
+        self.common._update_login_info()
+        # First, add a host if it is not added before.
+        host_id = self.common.add_host(connector['host'])
+        # Then, add free FC ports to the host.
+        ini_wwns = connector['wwpns']
+        free_wwns = self._get_connected_free_wwns()
+        for wwn in free_wwns:
+            if wwn in ini_wwns:
+                self._add_fc_port_to_host(host_id, wwn)
+        fc_port_details = self._get_host_port_details(host_id)
+        tgt_wwns = self._get_tgt_fc_port_wwns(fc_port_details)
+
+        LOG.debug(_('initialize_connection: Target FC ports WWNS: %s')
+                  % tgt_wwns)
+
+        # Finally, map the volume to the host.
+        volume_id = volume['provider_location']
+        hostlun_id = self.common.map_volume(host_id, volume_id)
+
+        # Change LUN ctr for better performance, just for single path.
+        if len(tgt_wwns) == 1:
+            lun_details = self.common.get_lun_details(volume_id)
+            port_ctr = self._get_fc_port_ctr(fc_port_details[0])
+            if (lun_details['LunType'] == 'THICK' and
+                    lun_details['OwningController'] != port_ctr):
+                self.common.change_lun_ctr(volume_id, port_ctr)
+
+        properties = {}
+        properties['target_discovered'] = False
+        properties['target_wwn'] = tgt_wwns
+        properties['target_lun'] = int(hostlun_id)
+        properties['volume_id'] = volume['id']
+
+        return {'driver_volume_type': 'fibre_channel',
+                'data': properties}
+
+    def _get_connected_free_wwns(self):
+        """Get free connected FC port WWNs.
+
+        If no new ports connected, return an empty list.
+
+        """
+
+        cli_cmd = 'showfreeport'
+        out = self.common._execute_cli(cli_cmd)
+        wwns = []
+        if re.search('Host Free Port Information', out):
+            for line in out.split('\r\n')[6:-2]:
+                tmp_line = line.split()
+                if (tmp_line[1] == 'FC') and (tmp_line[4] == 'Connected'):
+                    wwns.append(tmp_line[0])
+
+        return wwns
+
+    def _add_fc_port_to_host(self, hostid, wwn, multipathtype=0):
+        """Add a FC port to host."""
+        portname = HOST_PORT_PREFIX + wwn
+        cli_cmd = ('addhostport -host %(id)s -type 1 '
+                   '-wwn %(wwn)s -n %(name)s -mtype %(multype)s'
+                   % {'id': hostid,
+                      'wwn': wwn,
+                      'name': portname,
+                      'multype': multipathtype})
+        out = self.common._execute_cli(cli_cmd)
+
+        msg = ('Failed to add FC port %(port)s to host %(host)s.'
+               % {'port': portname, 'host': hostid})
+        self._assert_cli_operate_out('_add_fc_port_to_host', msg, cli_cmd, out)
+
+    def _get_host_port_details(self, host_id):
+        cli_cmd = 'showhostpath -host %s' % host_id
+        out = self.common._execute_cli(cli_cmd)
+
+        self._assert_cli_out(re.search('Multi Path Information', out),
+                             '_get_host_port_details',
+                             'Failed to get host port details.',
+                             cli_cmd, out)
+
+        port_details = []
+        tmp_details = {}
+        for line in out.split('\r\n')[4:-2]:
+            line = line.split('|')
+            # Cut-point of multipal details, usually is "-------".
+            if len(line) == 1:
+                port_details.append(tmp_details)
+                continue
+            key = ''.join(line[0].strip().split())
+            val = line[1].strip()
+            tmp_details[key] = val
+        port_details.append(tmp_details)
+        return port_details
+
+    def _get_tgt_fc_port_wwns(self, port_details):
+        wwns = []
+        for port in port_details:
+            wwns.append(port['TargetWWN'])
+        return wwns
+
+    def _get_fc_port_ctr(self, port_details):
+        return port_details['ControllerID']
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Terminate the map."""
+        LOG.debug(_('terminate_connection: volume: %(vol)s host: %(host)s '
+                    'connector: %(initiator)s')
+                  % {'vol': volume['name'],
+                     'host': connector['host'],
+                     'initiator': connector['initiator']})
+
+        self.common._update_login_info()
+        host_id = self.common.remove_map(volume['provider_location'],
+                                         connector['host'])
+        # Remove all FC ports and delete the host if
+        # no volume mapping to it.
+        if not self.common._get_host_map_info(host_id):
+            self._remove_fc_ports(host_id, connector)
+
+    def _remove_fc_ports(self, hostid, connector):
+        """Remove FC ports and delete host."""
+        wwns = connector['wwpns']
+        port_num = 0
+        port_info = self.common._get_host_port_info(hostid)
+        if port_info:
+            port_num = len(port_info)
+            for port in port_info:
+                if port[2] in wwns:
+                    self.common._delete_hostport(port[0])
+                    port_num -= 1
+        else:
+            LOG.warn(_('_remove_fc_ports: FC port was not found '
+                       'on host %(hostid)s.') % {'hostid': hostid})
+
+        if port_num == 0:
+            self.common._delete_host(hostid)
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume stats."""
+        self._stats = self.common.get_volume_stats(refresh)
+        self._stats['storage_protocol'] = 'FC'
+        self._stats['driver_version'] = self.VERSION
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        self._stats['volume_backend_name'] = (backend_name or
+                                              self.__class__.__name__)
+        return self._stats