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
This commit is contained in:
Raunak Kumar 2016-08-23 14:46:24 -07:00
parent ac179a3a43
commit c3d1dd1048
3 changed files with 456 additions and 66 deletions

View File

@ -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 = """
<simpleType name="SmErrorType">
@ -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',

View File

@ -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})

View File

@ -0,0 +1,3 @@
---
features:
- Added Nimble Storage Fibre Channel backend driver.