From c3d1dd1048bff4976bc771539d1f44cc423b7adf Mon Sep 17 00:00:00 2001 From: Raunak Kumar Date: Tue, 23 Aug 2016 14:46:24 -0700 Subject: [PATCH] Add Fibre Channel support for Nimble Storage Support all the basic volume driver features which are also supported by the NimbleISCSIDriver DocImpact Implements: blueprint nimble-add-fc-support Change-Id: I83b62ab42c6224509b7fef5e72a692a559b2e56d --- .../tests/unit/volume/drivers/test_nimble.py | 162 +++++++- cinder/volume/drivers/nimble.py | 357 ++++++++++++++---- ...imble-add-fc-support-0007fdbd647be947.yaml | 3 + 3 files changed, 456 insertions(+), 66 deletions(-) create mode 100644 releasenotes/notes/nimble-add-fc-support-0007fdbd647be947.yaml diff --git a/cinder/tests/unit/volume/drivers/test_nimble.py b/cinder/tests/unit/volume/drivers/test_nimble.py index d7e9f7b6d8e..14265d17d2b 100644 --- a/cinder/tests/unit/volume/drivers/test_nimble.py +++ b/cinder/tests/unit/volume/drivers/test_nimble.py @@ -33,7 +33,9 @@ NIMBLE_CLIENT = 'cinder.volume.drivers.nimble.client' NIMBLE_URLLIB2 = 'six.moves.urllib.request' NIMBLE_RANDOM = 'cinder.volume.drivers.nimble.random' NIMBLE_ISCSI_DRIVER = 'cinder.volume.drivers.nimble.NimbleISCSIDriver' -DRIVER_VERSION = '3.0.0' +NIMBLE_BASE_DRIVER = 'cinder.volume.drivers.nimble.NimbleBaseVolumeDriver' +NIMBLE_FC_DRIVER = 'cinder.volume.drivers.nimble.NimbleFCDriver' +DRIVER_VERSION = '3.1.0' FAKE_ENUM_STRING = """ @@ -115,6 +117,15 @@ FAKE_IGROUP_LIST_RESPONSE = { {'initiator-list': [{'name': 'test-initiator1'}], 'name': 'test-igrp2'}]} +FAKE_IGROUP_LIST_RESPONSE_FC = { + 'err-list': {'err-list': [{'code': 0}]}, + 'initiatorgrp-list': [ + {'initiator-list': [{'wwpn': '10:00:00:00:00:00:00:00'}], + 'name': 'test-igrp2'}, + {'initiator-list': [{'wwpn': '10:00:00:00:00:00:00:00'}, + {'wwpn': '10:00:00:00:00:00:00:01'}], + 'name': 'test-igrp1'}]} + FAKE_GET_VOL_INFO_RESPONSE = { 'err-list': {'err-list': [{'code': 0}]}, 'vol': {'target-name': 'iqn.test', @@ -205,6 +216,27 @@ class NimbleDriverBaseTestCase(test.TestCase): return inner_client_mock return client_mock_wrapper + @staticmethod + def client_mock_decorator_fc(configuration): + def client_mock_wrapper(func): + def inner_clent_mock( + self, mock_client_class, mock_urllib2, *args, **kwargs): + self.mock_client_class = mock_client_class + self.mock_client_service = mock.MagicMock(name='Client') + self.mock_client_class.Client.return_value = ( + self.mock_client_service) + mock_wsdl = mock_urllib2.urlopen.return_value + mock_wsdl.read = mock.MagicMock() + mock_wsdl.read.return_value = FAKE_ENUM_STRING + self.driver = nimble.NimbleFCDriver( + configuration=configuration) + self.mock_client_service.service.login.return_value = ( + FAKE_POSITIVE_LOGIN_RESPONSE_1) + self.driver.do_setup(context.get_admin_context()) + func(self, *args, **kwargs) + return inner_clent_mock + return client_mock_wrapper + def tearDown(self): super(NimbleDriverBaseTestCase, self).tearDown() @@ -1009,6 +1041,53 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): self.mock_client_service.method_calls, expected_call_list) + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_lun_number") + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + def test_initialize_connection_fc_igroup_exist(self, mock_wwpns, + mock_lun_number): + mock_lun_number.return_value = 13 + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.service.getInitiatorGrpList.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + expected_res = { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'target_lun': 13, + 'target_discovered': True, + 'target_wwn': ["1111111111111101"], + 'initiator_target_map': {'1000000000000000': + ['1111111111111101']}}} + self.assertEqual( + expected_res, + self.driver.initialize_connection( + {'name': 'test-volume', + 'provider_location': 'array1', + 'id': 12}, + {'initiator': 'test-initiator1', + 'wwpns': ['1000000000000000']})) + expected_call_list = [mock.call.set_options( + location='https://10.18.108.55:5391/soap'), + mock.call.service.login( + req={ + 'username': 'nimble', 'password': 'nimble_pass'}), + mock.call.service.getInitiatorGrpList( + request={'sid': 'a9b9aba7'}), + mock.call.service.addVolAcl( + request={'volname': 'test-volume', + 'apply-to': 3, + 'chapuser': '*', + 'initiatorgrp': 'test-igrp2', + 'sid': 'a9b9aba7'})] + self.assertEqual( + self.mock_client_service.method_calls, + expected_call_list) + @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @@ -1053,6 +1132,57 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): self.mock_client_service.method_calls, expected_calls) + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + @mock.patch(NIMBLE_FC_DRIVER + ".get_lun_number") + @mock.patch(NIMBLE_RANDOM) + def test_initialize_connection_fc_igroup_not_exist(self, mock_random, + mock_lun_number, + mock_wwpns): + mock_random.sample.return_value = 'abcdefghijkl' + mock_lun_number.return_value = 13 + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.service.getInitiatorGrpList.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + expected_res = { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'target_lun': 13, + 'target_discovered': True, + 'target_wwn': ["1111111111111101"], + 'initiator_target_map': {'1000000000000000': + ['1111111111111101']}}} + + self.assertEqual( + expected_res, + self.driver.initialize_connection( + {'name': 'test-volume', + 'provider_location': 'array1', + 'id': 12}, + {'initiator': 'test-initiator3', + 'wwpns': ['1000000000000000']})) + expected_calls = [ + mock.call.service.getInitiatorGrpList( + request={'sid': 'a9b9aba7'}), + mock.call.service.createInitiatorGrp( + request={ + 'attr': {'initiator-list': [{'wwn': '1000000000000000'}], + 'name': 'openstack-abcdefghijkl'}, + 'sid': 'a9b9aba7'}), + mock.call.service.addVolAcl( + request={'volname': 'test-volume', 'apply-to': 3, + 'chapuser': '*', + 'initiatorgrp': 'openstack-abcdefghijkl', + 'sid': 'a9b9aba7'})] + self.mock_client_service.assert_has_calls( + self.mock_client_service.method_calls, + expected_calls) + @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @@ -1080,6 +1210,36 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): self.mock_client_service.method_calls, expected_calls) + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + def test_terminate_connection_positive_fc(self, mock_wwpns): + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.service.getInitiatorGrpList.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + self.driver.terminate_connection( + {'name': 'test-volume', + 'provider_location': 'array1', + 'id': 12}, + {'initiator': 'test-initiator1', + 'wwpns': ['1000000000000000']}) + expected_calls = [mock.call.service.getInitiatorGrpList( + request={'sid': 'a9b9aba7'}), + mock.call.service.removeVolAcl( + request={'volname': 'test-volume', + 'apply-to': 3, + 'chapuser': '*', + 'initiatorgrp': {'initiator-list': + [{'wwn': '1000000000000000'}]}, + 'sid': 'a9b9aba7'})] + self.mock_client_service.assert_has_calls( + self.mock_client_service.method_calls, + expected_calls) + @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', diff --git a/cinder/volume/drivers/nimble.py b/cinder/volume/drivers/nimble.py index 3d1d2d9c93e..9e7f4f3de12 100644 --- a/cinder/volume/drivers/nimble.py +++ b/cinder/volume/drivers/nimble.py @@ -18,6 +18,7 @@ Volume driver for Nimble Storage. This driver supports Nimble Storage controller CS-Series. """ +import abc import functools import math import random @@ -37,11 +38,13 @@ from cinder import exception from cinder.i18n import _, _LE, _LI, _LW from cinder import interface from cinder.objects import volume +from cinder.volume import driver from cinder.volume.drivers.san import san from cinder.volume import volume_types +from cinder.zonemanager import utils as fczm_utils -DRIVER_VERSION = '3.0.0' +DRIVER_VERSION = '3.1.0' AES_256_XTS_CIPHER = 2 DEFAULT_CIPHER = 3 EXTRA_SPEC_ENCRYPTION = 'nimble:encryption' @@ -63,6 +66,8 @@ SM_SUBNET_DATA = 3 SM_SUBNET_MGMT_PLUS_DATA = 4 LUN_ID = '0' WARN_LEVEL = 0.8 +ISCSI_TARGET_PORT = ':3260' +FIBRE_CHANNEL_PROTOCOL = 2 # Work around for ubuntu_openssl_bug_965371. Python soap client suds # throws the error ssl-certificate-verify-failed-error, workaround to disable @@ -93,9 +98,7 @@ class NimbleAPIException(exception.VolumeBackendAPIException): message = _("Unexpected response from Nimble API") -@interface.volumedriver -class NimbleISCSIDriver(san.SanISCSIDriver): - +class NimbleBaseVolumeDriver(san.SanDriver): """OpenStack driver to enable Nimble Controller. Version history: @@ -111,6 +114,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver): 2.0.1 - Added multi-initiator support through extra-specs 2.0.2 - Fixed supporting extra specs while cloning vols 3.0.0 - Newton Support for Force Backup + 3.1.0 - Fibre Channel Support """ VERSION = DRIVER_VERSION @@ -119,9 +123,10 @@ class NimbleISCSIDriver(san.SanISCSIDriver): CI_WIKI_NAME = "Nimble_Storage_CI" def __init__(self, *args, **kwargs): - super(NimbleISCSIDriver, self).__init__(*args, **kwargs) + super(NimbleBaseVolumeDriver, self).__init__(*args, **kwargs) self.APIExecutor = None self.group_stats = {} + self._storage_protocol = None self.configuration.append_config_values(nimble_opts) def _check_config(self): @@ -132,43 +137,6 @@ class NimbleISCSIDriver(san.SanISCSIDriver): raise exception.InvalidInput(reason=_('%s is not set.') % attr) - def _get_discovery_ip(self, netconfig): - """Get discovery ip.""" - subnet_label = self.configuration.nimble_subnet_label - LOG.debug('subnet_label used %(netlabel)s, netconfig %(netconf)s', - {'netlabel': subnet_label, 'netconf': netconfig}) - ret_discovery_ip = '' - for subnet in netconfig['subnet-list']: - LOG.info(_LI('Exploring array subnet label %s'), subnet['label']) - if subnet_label == '*': - # Use the first data subnet, save mgmt+data for later - if subnet['subnet-id']['type'] == SM_SUBNET_DATA: - LOG.info(_LI('Discovery ip %(disc_ip)s is used ' - 'on data subnet %(net_label)s'), - {'disc_ip': subnet['discovery-ip'], - 'net_label': subnet['label']}) - return subnet['discovery-ip'] - elif (subnet['subnet-id']['type'] == - SM_SUBNET_MGMT_PLUS_DATA): - LOG.info(_LI('Discovery ip %(disc_ip)s is found' - ' on mgmt+data subnet %(net_label)s'), - {'disc_ip': subnet['discovery-ip'], - 'net_label': subnet['label']}) - ret_discovery_ip = subnet['discovery-ip'] - # If subnet is specified and found, use the subnet - elif subnet_label == subnet['label']: - LOG.info(_LI('Discovery ip %(disc_ip)s is used' - ' on subnet %(net_label)s'), - {'disc_ip': subnet['discovery-ip'], - 'net_label': subnet['label']}) - return subnet['discovery-ip'] - if ret_discovery_ip: - LOG.info(_LI('Discovery ip %s is used on mgmt+data subnet'), - ret_discovery_ip) - return ret_discovery_ip - else: - raise NimbleDriverException(_('No suitable discovery ip found')) - def _update_existing_vols_agent_type(self, context): LOG.debug("Updating existing volumes to have " "agent_type = 'OPENSTACK'") @@ -200,24 +168,18 @@ class NimbleISCSIDriver(san.SanISCSIDriver): raise self._update_existing_vols_agent_type(context) - def _get_provider_location(self, volume_name): - """Get volume iqn for initiator access.""" - vol_info = self.APIExecutor.get_vol_info(volume_name) - iqn = vol_info['target-name'] - netconfig = self.APIExecutor.get_netconfig('active') - target_ipaddr = self._get_discovery_ip(netconfig) - iscsi_portal = target_ipaddr + ':3260' - provider_location = '%s %s %s' % (iscsi_portal, iqn, LUN_ID) - LOG.info(_LI('vol_name=%(name)s provider_location=%(loc)s'), - {'name': volume_name, 'loc': provider_location}) - return provider_location - def _get_model_info(self, volume_name): """Get model info for the volume.""" return ( {'provider_location': self._get_provider_location(volume_name), 'provider_auth': None}) + @abc.abstractmethod + def _get_provider_location(self, volume_name): + """Volume info for iSCSI and FC""" + + pass + def create_volume(self, volume): """Create a new volume.""" reserve = not self.configuration.san_thin_provision @@ -368,7 +330,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver): self.group_stats = {'volume_backend_name': backend_name, 'vendor_name': 'Nimble', 'driver_version': DRIVER_VERSION, - 'storage_protocol': 'iSCSI'} + 'storage_protocol': self._storage_protocol} # Just use a single pool for now, FIXME to support multiple # pools single_pool = dict( @@ -491,15 +453,6 @@ class NimbleISCSIDriver(san.SanISCSIDriver): self.APIExecutor.online_vol(vol_name, False, ignore_list=[ 'SM-enoent']) - def _create_igroup_for_initiator(self, initiator_name): - """Creates igroup for an initiator and returns the igroup name.""" - igrp_name = 'openstack-' + self._generate_random_string(12) - LOG.info(_LI('Creating initiator group %(grp)s ' - 'with initiator %(iname)s'), - {'grp': igrp_name, 'iname': initiator_name}) - self.APIExecutor.create_initiator_group(igrp_name, initiator_name) - return igrp_name - def _get_igroupname_for_initiator(self, initiator_name): initiator_groups = self.APIExecutor.get_initiator_grp_list() for initiator_group in initiator_groups: @@ -515,6 +468,51 @@ class NimbleISCSIDriver(san.SanISCSIDriver): LOG.info(_LI('No igroup found for initiator %s'), initiator_name) return '' + +@interface.volumedriver +class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver): + """OpenStack driver to enable Nimble ISCSI Controller.""" + + def __init__(self, *args, **kwargs): + super(NimbleISCSIDriver, self).__init__(*args, **kwargs) + self._storage_protocol = "iSCSI" + + def _get_discovery_ip(self, netconfig): + """Get discovery ip.""" + subnet_label = self.configuration.nimble_subnet_label + LOG.debug('subnet_label used %(netlabel)s, netconfig %(netconf)s', + {'netlabel': subnet_label, 'netconf': netconfig}) + ret_discovery_ip = '' + for subnet in netconfig['subnet-list']: + LOG.info(_LI('Exploring array subnet label %s'), subnet['label']) + if subnet_label == '*': + # Use the first data subnet, save mgmt+data for later + if subnet['subnet-id']['type'] == SM_SUBNET_DATA: + LOG.info(_LI('Discovery ip %(disc_ip)s is used ' + 'on data subnet %(net_label)s'), + {'disc_ip': subnet['discovery-ip'], + 'net_label': subnet['label']}) + return subnet['discovery-ip'] + elif (subnet['subnet-id']['type'] == + SM_SUBNET_MGMT_PLUS_DATA): + LOG.info(_LI('Discovery ip %(disc_ip)s is found' + ' on mgmt+data subnet %(net_label)s'), + {'disc_ip': subnet['discovery-ip'], + 'net_label': subnet['label']}) + ret_discovery_ip = subnet['discovery-ip'] + # If subnet is specified and found, use the subnet + elif subnet_label == subnet['label']: + LOG.info(_LI('Discovery ip %(disc_ip)s is used' + ' on subnet %(net_label)s'), + {'disc_ip': subnet['discovery-ip'], + 'net_label': subnet['label']}) + return subnet['discovery-ip'] + if ret_discovery_ip: + LOG.info(_LI('Discovery ip %s is used on mgmt+data subnet'), + ret_discovery_ip) + return ret_discovery_ip + raise NimbleDriverException(_('No suitable discovery ip found')) + def initialize_connection(self, volume, connector): """Driver entry point to attach a volume to an instance.""" LOG.info(_LI('Entering initialize_connection volume=%(vol)s' @@ -526,7 +524,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver): initiator_group_name = self._get_igroupname_for_initiator( initiator_name) if not initiator_group_name: - initiator_group_name = self._create_igroup_for_initiator( + initiator_group_name = self._create_igroup_for_initiator_iscsi( initiator_name) LOG.info(_LI('Initiator group name is %(grp)s for initiator ' '%(iname)s'), @@ -560,6 +558,203 @@ class NimbleISCSIDriver(san.SanISCSIDriver): initiator_name) self.APIExecutor.remove_acl(volume, initiator_group_name) + def _create_igroup_for_initiator_iscsi(self, initiator_name): + """Creates igroup for an initiator and returns the igroup name.""" + igrp_name = 'openstack-' + self._generate_random_string(12) + LOG.info(_LI('Creating initiator group %(grp)s ' + 'with initiator %(iname)s'), + {'grp': igrp_name, 'iname': initiator_name}) + self.APIExecutor.create_initiator_group(igrp_name, initiator_name) + return igrp_name + + def _get_provider_location(self, volume_name): + """Get volume iqn for initiator access.""" + vol_info = self.APIExecutor.get_vol_info(volume_name) + iqn = vol_info['target-name'] + netconfig = self.APIExecutor.get_netconfig('active') + target_ipaddr = self._get_discovery_ip(netconfig) + iscsi_portal = target_ipaddr + ISCSI_TARGET_PORT + provider_location = '%s %s %s' % (iscsi_portal, iqn, LUN_ID) + LOG.info(_LI('vol_name=%(name)s provider_location=%(loc)s'), + {'name': volume_name, 'loc': provider_location}) + return provider_location + + +@interface.volumedriver +class NimbleFCDriver(NimbleBaseVolumeDriver, driver.FibreChannelDriver): + """OpenStack driver to enable Nimble FC Driver Controller.""" + + def __init__(self, *args, **kwargs): + super(NimbleFCDriver, self).__init__(*args, **kwargs) + self._storage_protocol = "FC" + self._lookup_service = fczm_utils.create_lookup_service() + + def _get_provider_location(self, volume_name): + """Get array info wwn details.""" + netconfig = self.APIExecutor.get_netconfig('active') + array_name = netconfig['array-list'][0]['array-name'] + provider_location = '%s' % (array_name) + LOG.info(_LI('vol_name=%(name)s provider_location=%(loc)s'), + {'name': volume_name, 'loc': provider_location}) + return provider_location + + def _build_initiator_target_map(self, target_wwns, connector): + """Build the target_wwns and the initiator target map.""" + init_targ_map = {} + + if self._lookup_service: + # use FC san lookup to determine which wwpns to use + # for the new VLUN. + dev_map = self._lookup_service.get_device_mapping_from_network( + connector['wwpns'], + target_wwns) + map_fabric = dev_map + LOG.info(_LI("dev_map =%(fabric)s"), {'fabric': map_fabric}) + + for fabric_name in dev_map: + fabric = dev_map[fabric_name] + 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])) + else: + init_targ_map = dict.fromkeys(connector["wwpns"], target_wwns) + + return init_targ_map + + @fczm_utils.AddFCZone + def initialize_connection(self, volume, connector): + """Driver entry point to attach a volume to an instance.""" + LOG.info(_LI('Entering initialize_connection volume=%(vol)s' + ' connector=%(conn)s location=%(loc)s'), + {'vol': volume, + 'conn': connector, + 'loc': volume['provider_location']}) + wwpns = [] + initiator_name = connector['initiator'] + for wwpn in connector['wwpns']: + wwpns.append(wwpn) + initiator_group_name = self._get_igroupname_for_initiator_fc(wwpns) + + if not initiator_group_name: + initiator_group_name = self._create_igroup_for_initiator_fc( + initiator_name, wwpns) + + LOG.info(_LI('Initiator group name is %(grp)s for initiator ' + '%(iname)s'), + {'grp': initiator_group_name, 'iname': initiator_name}) + self.APIExecutor.add_acl(volume, initiator_group_name) + + lun = self.get_lun_number(volume, initiator_group_name) + init_targ_map = {} + array_name = volume['provider_location'].split() + + target_wwns = self.get_wwpns_from_array(array_name) + + init_targ_map = self._build_initiator_target_map(target_wwns, + connector) + + data = {'driver_volume_type': 'fibre_channel', + 'data': {'target_lun': lun, + 'target_discovered': True, + 'target_wwn': target_wwns, + 'initiator_target_map': init_targ_map}} + + LOG.info(_LI("Return FC data for zone addition: %(data)s."), + {'data': data}) + + return data + + @fczm_utils.RemoveFCZone + def terminate_connection(self, volume, connector, **kwargs): + """Driver entry point to unattach a volume from an instance.""" + LOG.info(_LI('Entering terminate_connection volume=%(vol)s' + ' connector=%(conn)s location=%(loc)s.'), + {'vol': volume, + 'conn': connector, + 'loc': volume['provider_location']}) + + wwpns = [] + initiator_name = connector['initiator'] + for wwpn in connector['wwpns']: + wwpns.append(wwpn) + init_targ_map = {} + array_name = volume['provider_location'].split() + target_wwns = self.get_wwpns_from_array(array_name) + init_targ_map = self._build_initiator_target_map(target_wwns, + connector) + initiator_group_name = self._get_igroupname_for_initiator_fc(wwpns) + if not initiator_group_name: + raise NimbleDriverException( + _('No initiator group found for initiator %s') % + initiator_name) + LOG.debug("initiator_target_map".format(init_targ_map)) + self.APIExecutor.remove_acl(volume, initiator_group_name) + # FIXME to check for other volumes attached to the host and then + # return the data. Bug https://bugs.launchpad.net/cinder/+bug/1617472 + + data = {'driver_volume_type': 'fibre_channel', + 'data': {}} + + return data + + def _get_igroupname_for_initiator_fc(self, initiator_wwpns): + initiator_groups = self.APIExecutor.get_initiator_grp_list() + for initiator_group in initiator_groups: + if 'initiator-list' in initiator_group: + wwpns_list = [] + for initiator in initiator_group['initiator-list']: + wwpn = str(initiator['wwpn']).replace(":", "") + wwpns_list.append(wwpn) + LOG.debug("initiator_wwpns= %s wwpns_list_from_array=%s", + initiator_wwpns, wwpns_list) + if set(initiator_wwpns) == set(wwpns_list): + LOG.info(_LI('igroup %(grp)s found for ' + 'initiator %(wwpns_list)s'), + {'grp': initiator_group['name'], + 'wwpns_list': wwpns_list}) + return initiator_group['name'] + LOG.info(_LI('No igroup found for initiator %s'), initiator_wwpns) + return '' + + def get_lun_number(self, volume, initiator_group_name): + vol_info = self.APIExecutor.get_vol_info(volume['name']) + for acl in vol_info['aclList']: + if (initiator_group_name == acl['initiatorgrp']): + LOG.info(_LI("acllist =%(aclList)s"), + {'aclList': vol_info['aclList']}) + lun = vol_info['aclList'][0]['lun'] + return lun + + def _create_igroup_for_initiator_fc(self, initiator_name, wwpn_list): + """Creates igroup for an initiator and returns the igroup name.""" + igrp_name = 'openstack-' + self._generate_random_string(12) + LOG.info(_LI('Creating initiator group %(grp)s ' + 'with initiator %(iname)s'), + {'grp': igrp_name, 'iname': initiator_name}) + self.APIExecutor.create_initiator_group_fc(igrp_name, initiator_name, + wwpn_list) + return igrp_name + + def get_wwpns_from_array(self, array_name): + """Retrieve the wwpns from the array""" + target_wwpns = [] + interface_info = self.APIExecutor.get_fc_interface_list(array_name) + + for wwpn_list in interface_info['ctrlr-a-interface-list']: + wwpn = wwpn_list['wwpn'] + wwpn = wwpn.replace(":", "") + target_wwpns.append(wwpn) + + for wwpn_list in interface_info['ctrlr-b-interface-list']: + wwpn = wwpn_list['wwpn'] + wwpn = wwpn.replace(":", "") + target_wwpns.append(wwpn) + + return target_wwpns + def _response_checker(func): """Decorator function to check if the response of an API is positive.""" @@ -607,6 +802,7 @@ class NimbleAPIExecutor(object): self.sid = None self.username = kwargs['username'] self.password = kwargs['password'] + self.ip = kwargs['ip'] wsdl_url = 'https://%s/wsdl/NsGroupManagement.wsdl' % (kwargs['ip']) LOG.debug('Using Nimble wsdl_url: %s', wsdl_url) @@ -982,6 +1178,29 @@ class NimbleAPIExecutor(object): return (response['initiatorgrp-list'] if 'initiatorgrp-list' in response else []) + @_connection_checker + @_response_checker + def create_initiator_group_fc(self, initiator_group_name, initiator_name, + wwpn_list): + """Execute createInitiatorGrp API for Fibre Channel""" + LOG.info(_LI('Creating initiator group %(igrp)s' + ' with wwpn %(wwpn)s'), + {'igrp': initiator_group_name, 'wwpn': wwpn_list}) + request = {} + request['sid'] = self.sid + request['attr'] = {} + request['attr']['name'] = initiator_group_name + request['attr']['access-protocol'] = FIBRE_CHANNEL_PROTOCOL + request['attr']['initiator-list'] = [] + for wwpn in wwpn_list: + initiator = {} + initiator['access-protocol'] = FIBRE_CHANNEL_PROTOCOL + initiator['wwpn'] = wwpn + request['attr']['initiator-list'].append(initiator) + LOG.debug("createInitiatorGrp request %s", request) + + return self.client.service.createInitiatorGrp(request=request) + @_connection_checker @_response_checker def create_initiator_group(self, initiator_group_name, initiator_name): @@ -1003,3 +1222,11 @@ class NimbleAPIExecutor(object): return self.client.service.deleteInitiatorGrp( request={'sid': self.sid, 'name': initiator_group_name}) + + @_connection_checker + @_response_checker + def get_fc_interface_list(self, array_name): + """getFibreChannelInterfaceList API to get FC interfaces on array""" + return self.client.service.getFibreChannelInterfaceList( + request={'sid': self.sid, + 'name-or-serial': array_name}) diff --git a/releasenotes/notes/nimble-add-fc-support-0007fdbd647be947.yaml b/releasenotes/notes/nimble-add-fc-support-0007fdbd647be947.yaml new file mode 100644 index 00000000000..2f7e6b5554e --- /dev/null +++ b/releasenotes/notes/nimble-add-fc-support-0007fdbd647be947.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added Nimble Storage Fibre Channel backend driver.