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_URLLIB2 = 'six.moves.urllib.request'
NIMBLE_RANDOM = 'cinder.volume.drivers.nimble.random' NIMBLE_RANDOM = 'cinder.volume.drivers.nimble.random'
NIMBLE_ISCSI_DRIVER = 'cinder.volume.drivers.nimble.NimbleISCSIDriver' 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 = """ FAKE_ENUM_STRING = """
<simpleType name="SmErrorType"> <simpleType name="SmErrorType">
@ -115,6 +117,15 @@ FAKE_IGROUP_LIST_RESPONSE = {
{'initiator-list': [{'name': 'test-initiator1'}], {'initiator-list': [{'name': 'test-initiator1'}],
'name': 'test-igrp2'}]} '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 = { FAKE_GET_VOL_INFO_RESPONSE = {
'err-list': {'err-list': [{'code': 0}]}, 'err-list': {'err-list': [{'code': 0}]},
'vol': {'target-name': 'iqn.test', 'vol': {'target-name': 'iqn.test',
@ -205,6 +216,27 @@ class NimbleDriverBaseTestCase(test.TestCase):
return inner_client_mock return inner_client_mock
return client_mock_wrapper 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): def tearDown(self):
super(NimbleDriverBaseTestCase, self).tearDown() super(NimbleDriverBaseTestCase, self).tearDown()
@ -1009,6 +1041,53 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
self.mock_client_service.method_calls, self.mock_client_service.method_calls,
expected_call_list) 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_URLLIB2)
@mock.patch(NIMBLE_CLIENT) @mock.patch(NIMBLE_CLIENT)
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
@ -1053,6 +1132,57 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
self.mock_client_service.method_calls, self.mock_client_service.method_calls,
expected_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_URLLIB2)
@mock.patch(NIMBLE_CLIENT) @mock.patch(NIMBLE_CLIENT)
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
@ -1080,6 +1210,36 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
self.mock_client_service.method_calls, self.mock_client_service.method_calls,
expected_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_URLLIB2)
@mock.patch(NIMBLE_CLIENT) @mock.patch(NIMBLE_CLIENT)
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @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. This driver supports Nimble Storage controller CS-Series.
""" """
import abc
import functools import functools
import math import math
import random import random
@ -37,11 +38,13 @@ from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW from cinder.i18n import _, _LE, _LI, _LW
from cinder import interface from cinder import interface
from cinder.objects import volume from cinder.objects import volume
from cinder.volume import driver
from cinder.volume.drivers.san import san from cinder.volume.drivers.san import san
from cinder.volume import volume_types 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 AES_256_XTS_CIPHER = 2
DEFAULT_CIPHER = 3 DEFAULT_CIPHER = 3
EXTRA_SPEC_ENCRYPTION = 'nimble:encryption' EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
@ -63,6 +66,8 @@ SM_SUBNET_DATA = 3
SM_SUBNET_MGMT_PLUS_DATA = 4 SM_SUBNET_MGMT_PLUS_DATA = 4
LUN_ID = '0' LUN_ID = '0'
WARN_LEVEL = 0.8 WARN_LEVEL = 0.8
ISCSI_TARGET_PORT = ':3260'
FIBRE_CHANNEL_PROTOCOL = 2
# Work around for ubuntu_openssl_bug_965371. Python soap client suds # Work around for ubuntu_openssl_bug_965371. Python soap client suds
# throws the error ssl-certificate-verify-failed-error, workaround to disable # 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") message = _("Unexpected response from Nimble API")
@interface.volumedriver class NimbleBaseVolumeDriver(san.SanDriver):
class NimbleISCSIDriver(san.SanISCSIDriver):
"""OpenStack driver to enable Nimble Controller. """OpenStack driver to enable Nimble Controller.
Version history: Version history:
@ -111,6 +114,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
2.0.1 - Added multi-initiator support through extra-specs 2.0.1 - Added multi-initiator support through extra-specs
2.0.2 - Fixed supporting extra specs while cloning vols 2.0.2 - Fixed supporting extra specs while cloning vols
3.0.0 - Newton Support for Force Backup 3.0.0 - Newton Support for Force Backup
3.1.0 - Fibre Channel Support
""" """
VERSION = DRIVER_VERSION VERSION = DRIVER_VERSION
@ -119,9 +123,10 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
CI_WIKI_NAME = "Nimble_Storage_CI" CI_WIKI_NAME = "Nimble_Storage_CI"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NimbleISCSIDriver, self).__init__(*args, **kwargs) super(NimbleBaseVolumeDriver, self).__init__(*args, **kwargs)
self.APIExecutor = None self.APIExecutor = None
self.group_stats = {} self.group_stats = {}
self._storage_protocol = None
self.configuration.append_config_values(nimble_opts) self.configuration.append_config_values(nimble_opts)
def _check_config(self): def _check_config(self):
@ -132,43 +137,6 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
raise exception.InvalidInput(reason=_('%s is not set.') % raise exception.InvalidInput(reason=_('%s is not set.') %
attr) 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): def _update_existing_vols_agent_type(self, context):
LOG.debug("Updating existing volumes to have " LOG.debug("Updating existing volumes to have "
"agent_type = 'OPENSTACK'") "agent_type = 'OPENSTACK'")
@ -200,24 +168,18 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
raise raise
self._update_existing_vols_agent_type(context) 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): def _get_model_info(self, volume_name):
"""Get model info for the volume.""" """Get model info for the volume."""
return ( return (
{'provider_location': self._get_provider_location(volume_name), {'provider_location': self._get_provider_location(volume_name),
'provider_auth': None}) 'provider_auth': None})
@abc.abstractmethod
def _get_provider_location(self, volume_name):
"""Volume info for iSCSI and FC"""
pass
def create_volume(self, volume): def create_volume(self, volume):
"""Create a new volume.""" """Create a new volume."""
reserve = not self.configuration.san_thin_provision reserve = not self.configuration.san_thin_provision
@ -368,7 +330,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
self.group_stats = {'volume_backend_name': backend_name, self.group_stats = {'volume_backend_name': backend_name,
'vendor_name': 'Nimble', 'vendor_name': 'Nimble',
'driver_version': DRIVER_VERSION, 'driver_version': DRIVER_VERSION,
'storage_protocol': 'iSCSI'} 'storage_protocol': self._storage_protocol}
# Just use a single pool for now, FIXME to support multiple # Just use a single pool for now, FIXME to support multiple
# pools # pools
single_pool = dict( single_pool = dict(
@ -491,15 +453,6 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
self.APIExecutor.online_vol(vol_name, False, ignore_list=[ self.APIExecutor.online_vol(vol_name, False, ignore_list=[
'SM-enoent']) '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): def _get_igroupname_for_initiator(self, initiator_name):
initiator_groups = self.APIExecutor.get_initiator_grp_list() initiator_groups = self.APIExecutor.get_initiator_grp_list()
for initiator_group in initiator_groups: 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) LOG.info(_LI('No igroup found for initiator %s'), initiator_name)
return '' 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): def initialize_connection(self, volume, connector):
"""Driver entry point to attach a volume to an instance.""" """Driver entry point to attach a volume to an instance."""
LOG.info(_LI('Entering initialize_connection volume=%(vol)s' 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_group_name = self._get_igroupname_for_initiator(
initiator_name) initiator_name)
if not initiator_group_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) initiator_name)
LOG.info(_LI('Initiator group name is %(grp)s for initiator ' LOG.info(_LI('Initiator group name is %(grp)s for initiator '
'%(iname)s'), '%(iname)s'),
@ -560,6 +558,203 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
initiator_name) initiator_name)
self.APIExecutor.remove_acl(volume, initiator_group_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): def _response_checker(func):
"""Decorator function to check if the response of an API is positive.""" """Decorator function to check if the response of an API is positive."""
@ -607,6 +802,7 @@ class NimbleAPIExecutor(object):
self.sid = None self.sid = None
self.username = kwargs['username'] self.username = kwargs['username']
self.password = kwargs['password'] self.password = kwargs['password']
self.ip = kwargs['ip']
wsdl_url = 'https://%s/wsdl/NsGroupManagement.wsdl' % (kwargs['ip']) wsdl_url = 'https://%s/wsdl/NsGroupManagement.wsdl' % (kwargs['ip'])
LOG.debug('Using Nimble wsdl_url: %s', wsdl_url) LOG.debug('Using Nimble wsdl_url: %s', wsdl_url)
@ -982,6 +1178,29 @@ class NimbleAPIExecutor(object):
return (response['initiatorgrp-list'] return (response['initiatorgrp-list']
if 'initiatorgrp-list' in response else []) 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 @_connection_checker
@_response_checker @_response_checker
def create_initiator_group(self, initiator_group_name, initiator_name): def create_initiator_group(self, initiator_group_name, initiator_name):
@ -1003,3 +1222,11 @@ class NimbleAPIExecutor(object):
return self.client.service.deleteInitiatorGrp( return self.client.service.deleteInitiatorGrp(
request={'sid': self.sid, request={'sid': self.sid,
'name': initiator_group_name}) '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.