Separate FlashSystem FC and iSCSI common code

The patch is mainly to split original FC driver code into common
code and FC driver for IBM FlashSystem. iSCSI driver merged in L-1
has been inherited from original FC driver for common functions
already.

In this patch,
* Separate FC driver into new driver and common code.
* Modify iSCSI driver to inherite the common code.
* Common function _get_node_data in iSCSI driver is moved into
common code.
* Add locks in initialize_connection and terminate_connection
for iSCSI driver.
* Remove flashsystem_multipath_enabled to use Cinder iSCSI
multipath support.

Implements: blueprint ibm-flashsystem-split-fc
Change-Id: I166a14e3eef370a22f4c0a675d451a3a4a6989f1
This commit is contained in:
xiaolei hu 2015-06-25 10:22:50 +08:00 committed by Edwin Wang
parent 2cd5904eb8
commit 73a5cb1f75
4 changed files with 510 additions and 485 deletions

View File

@ -34,7 +34,7 @@ from cinder import exception
from cinder import test
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.ibm import flashsystem
from cinder.volume.drivers.ibm import flashsystem_fc
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types
@ -656,7 +656,7 @@ class FlashSystemManagementSimulator(object):
self._next_cmd_error[cmd] = error
class FlashSystemFakeDriver(flashsystem.FlashSystemDriver):
class FlashSystemFakeDriver(flashsystem_fc.FlashSystemFCDriver):
def __init__(self, *args, **kwargs):
super(FlashSystemFakeDriver, self).__init__(*args, **kwargs)
@ -893,7 +893,7 @@ class FlashSystemDriverTestCase(test.TestCase):
vol2, self.connector)
# case 3: _get_vdisk_map_properties raises exception
with mock.patch.object(flashsystem.FlashSystemDriver,
with mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_get_vdisk_map_properties') as get_properties:
get_properties.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
@ -903,7 +903,7 @@ class FlashSystemDriverTestCase(test.TestCase):
# clear environment
self.driver.delete_volume(vol1)
@mock.patch.object(flashsystem.FlashSystemDriver,
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_create_and_copy_vdisk_data')
def test_flashsystem_create_snapshot(self, _create_and_copy_vdisk_data):
# case 1: good path
@ -923,7 +923,7 @@ class FlashSystemDriverTestCase(test.TestCase):
self.assertRaises(exception.InvalidVolume,
self.driver.create_snapshot, snap2)
@mock.patch.object(flashsystem.FlashSystemDriver,
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_delete_vdisk')
def test_flashsystem_delete_snapshot(self, _delete_vdisk):
vol1 = self._generate_vol_info(None)
@ -933,7 +933,7 @@ class FlashSystemDriverTestCase(test.TestCase):
vol1['status'])
self.driver.delete_snapshot(snap1)
@mock.patch.object(flashsystem.FlashSystemDriver,
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_create_and_copy_vdisk_data')
def test_flashsystem_create_volume_from_snapshot(
self, _create_and_copy_vdisk_data):
@ -966,7 +966,7 @@ class FlashSystemDriverTestCase(test.TestCase):
self.driver.create_volume_from_snapshot,
vol, snap)
@mock.patch.object(flashsystem.FlashSystemDriver,
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_create_and_copy_vdisk_data')
def test_flashsystem_create_cloned_volume(
self, _create_and_copy_vdisk_data):
@ -1002,7 +1002,7 @@ class FlashSystemDriverTestCase(test.TestCase):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.get_volume_stats, refresh=True)
@mock.patch.object(flashsystem.FlashSystemDriver,
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
'_copy_vdisk_data')
def test_flashsystem_create_and_copy_vdisk_data(self, _copy_vdisk_data):
# case 1: when volume does not exist
@ -1033,8 +1033,8 @@ class FlashSystemDriverTestCase(test.TestCase):
self.driver.delete_volume(vol2)
@mock.patch.object(volume_utils, 'copy_volume')
@mock.patch.object(flashsystem.FlashSystemDriver, '_scan_device')
@mock.patch.object(flashsystem.FlashSystemDriver, '_remove_device')
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver, '_scan_device')
@mock.patch.object(flashsystem_fc.FlashSystemFCDriver, '_remove_device')
@mock.patch.object(utils, 'brick_get_connector_properties')
def test_flashsystem_copy_vdisk_data(self,
_connector,

View File

@ -1,4 +1,4 @@
# Copyright 2014 - 2015 IBM Corporation.
# Copyright 2015 IBM Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -19,14 +19,11 @@ Volume driver for IBM FlashSystem storage systems.
Limitations:
1. Cinder driver only works when open_access_enabled=off.
2. Cinder driver only works when connection protocol is FC.
"""
import random
import re
import string
import threading
from oslo_concurrency import processutils
from oslo_config import cfg
@ -44,7 +41,6 @@ from cinder import utils
from cinder.volume.drivers.san import san
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types
from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
@ -54,13 +50,12 @@ FLASHSYSTEM_VOL_IOGRP = 0
flashsystem_opts = [
cfg.StrOpt('flashsystem_connection_protocol',
default='FC',
help='Connection protocol should be FC.'),
cfg.BoolOpt('flashsystem_multipath_enabled',
default=False,
help='Connect with multipath (FC only).'),
help='Connection protocol should be FC. '
'(Default is FC.)'),
cfg.BoolOpt('flashsystem_multihostmap_enabled',
default=True,
help='Allows vdisk to multi host mapping.')
help='Allows vdisk to multi host mapping. '
'(Default is True)')
]
CONF = cfg.CONF
@ -68,17 +63,19 @@ CONF.register_opts(flashsystem_opts)
class FlashSystemDriver(san.SanDriver):
"""IBM FlashSystem 840 FC volume driver.
"""IBM FlashSystem volume driver.
Version history:
1.0.0 - Initial driver
1.0.1 - Code clean up
1.0.2 - Add lock into vdisk map/unmap, connection
initialize/terminate
1.0.3 - Initial driver for iSCSI
1.0.4 - Split Flashsystem driver into common and FC
"""
VERSION = "1.0.1"
VERSION = "1.0.4"
def __init__(self, *args, **kwargs):
super(FlashSystemDriver, self).__init__(*args, **kwargs)
@ -134,16 +131,6 @@ class FlashSystemDriver(san.SanDriver):
map[idx].append(t_wwpn)
return map
def _check_vdisk_params(self, params):
# Check that the requested protocol is enabled
if params['protocol'] != self._protocol:
msg = (_("Illegal value '%(prot)s' specified for "
"flashsystem_connection_protocol: "
"valid value(s) are %(enabled)s.")
% {'prot': params['protocol'],
'enabled': self._protocol})
raise exception.InvalidInput(reason=msg)
def _connector_to_hostname_prefix(self, connector):
"""Translate connector info to storage system host name.
@ -172,8 +159,8 @@ class FlashSystemDriver(san.SanDriver):
invalid_ch_in_host, '-' * len(invalid_ch_in_host))
host_name = host_name.translate(string_host_name_filter)
else:
msg = (_('_create_host: Can not clean host name. Host name '
'is not unicode or string.'))
msg = _('_create_host: Can not translate host name. Host name '
'is not unicode or string.')
LOG.error(msg)
raise exception.NoValidHost(reason=msg)
@ -237,8 +224,7 @@ class FlashSystemDriver(san.SanDriver):
self.configuration.volume_dd_blocksize)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE('_copy_vdisk_data: Failed to '
'copy %(src)s to %(dest)s.'),
LOG.error(_LE('Failed to copy %(src)s to %(dest)s.'),
{'src': src_vdisk_name, 'dest': dest_vdisk_name})
finally:
if not dest_map:
@ -275,50 +261,6 @@ class FlashSystemDriver(san.SanDriver):
self._unset_vdisk_copy_in_progress(
[src_vdisk_name, dest_vdisk_name])
def _create_host(self, connector):
"""Create a new host on the storage system.
We create a host and associate it with the given connection
information.
"""
LOG.debug('enter: _create_host: host %s.', connector['host'])
rand_id = six.text_type(random.randint(0, 99999999)).zfill(8)
host_name = '%s-%s' % (self._connector_to_hostname_prefix(connector),
rand_id)
ports = []
if 'FC' == self._protocol and 'wwpns' in connector:
for wwpn in connector['wwpns']:
ports.append('-hbawwpn %s' % wwpn)
self._driver_assert(ports,
(_('_create_host: No connector ports.')))
port1 = ports.pop(0)
arg_name, arg_val = port1.split()
ssh_cmd = ['svctask', 'mkhost', '-force', arg_name, arg_val, '-name',
'"%s"' % host_name]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return('successfully created' in out,
'_create_host', ssh_cmd, out, err)
for port in ports:
arg_name, arg_val = port.split()
ssh_cmd = ['svctask', 'addhostport', '-force',
arg_name, arg_val, host_name]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
(not out.strip()),
'_create_host', ssh_cmd, out, err)
LOG.debug(
'leave: _create_host: host %(host)s - %(host_name)s.',
{'host': connector['host'], 'host_name': host_name})
return host_name
def _create_vdisk(self, name, size, unit, opts):
"""Create a new vdisk."""
@ -405,8 +347,8 @@ class FlashSystemDriver(san.SanDriver):
try:
out, err = self._ssh(ssh_cmd)
except processutils.ProcessExecutionError:
LOG.warning(_LW('_execute_command_and_parse_attributes: Failed to '
'run command: %s.'), ssh_cmd)
LOG.warning(_LW('Failed to run command: '
'%s.'), ssh_cmd)
# Does not raise exception when command encounters error.
# Only return and the upper logic decides what to do.
return None
@ -430,22 +372,6 @@ class FlashSystemDriver(san.SanDriver):
return attributes
def _find_host_exhaustive(self, connector, hosts):
for host in hosts:
ssh_cmd = ['svcinfo', 'lshost', '-delim', '!', host]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
out.strip(),
'_find_host_exhaustive', ssh_cmd, out, err)
for attr_line in out.split('\n'):
# If '!' not found, return the string and two empty strings
attr_name, foo, attr_val = attr_line.partition('!')
if (attr_name == 'WWPN' and
'wwpns' in connector and attr_val.lower() in
map(str.lower, map(str, connector['wwpns']))):
return host
return None
def _get_hdr_dic(self, header, row, delim):
"""Return CLI row data as a dictionary indexed by names from header.
string. The strings are converted to columns using the delimiter in
@ -462,38 +388,6 @@ class FlashSystemDriver(san.SanDriver):
dic = {a: v for a, v in map(None, attributes, values)}
return dic
def _get_conn_fc_wwpns(self):
wwpns = []
cmd = ['svcinfo', 'lsportfc']
generator = self._port_conf_generator(cmd)
header = next(generator, None)
if not header:
return wwpns
for port_data in generator:
try:
if port_data['status'] == 'active':
wwpns.append(port_data['WWPN'])
except KeyError:
self._handle_keyerror('lsportfc', header)
return wwpns
def _get_fc_wwpns(self):
for key in self._storage_nodes:
node = self._storage_nodes[key]
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!', node['id']]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
wwpns = set(node['WWPN'])
for i, s in zip(attributes['port_id'], attributes['port_status']):
if 'unconfigured' != s:
wwpns.add(i)
node['WWPN'] = list(wwpns)
LOG.info(_LI('WWPN on node %(node)s: %(wwpn)s.'),
{'node': node['id'], 'wwpn': node['WWPN']})
def _get_host_from_connector(self, connector):
"""List the hosts defined in the storage.
@ -546,6 +440,78 @@ class FlashSystemDriver(san.SanDriver):
return return_data
def _get_node_data(self):
"""Get and verify node configuration."""
# Get storage system name and id
ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes or not ('name' in attributes):
msg = _('Could not get system name.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self._system_name = attributes['name']
self._system_id = attributes['id']
# Validate value of open_access_enabled flag, for now only
# support when open_access_enabled is off
if not attributes or not ('open_access_enabled' in attributes) or (
attributes['open_access_enabled'] != 'off'):
msg = _('open_access_enabled is not off.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Validate that the array exists
pool = FLASHSYSTEM_VOLPOOL_NAME
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes:
msg = _('Unable to parse attributes.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if not ('status' in attributes) or (
attributes['status'] == 'offline'):
msg = (_('Array does not exist or is offline. '
'Current status of array is %s.')
% attributes['status'])
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
# Get the iSCSI names of the FlashSystem nodes
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
out.strip(), '_get_config_data', ssh_cmd, out, err)
nodes = out.strip().splitlines()
self._assert_ssh_return(nodes, '_get_node_data', ssh_cmd, out, err)
header = nodes.pop(0)
for node_line in nodes:
try:
node_data = self._get_hdr_dic(header, node_line, '!')
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
self._log_cli_output_error('_get_node_data',
ssh_cmd, out, err)
try:
node = {
'id': node_data['id'],
'name': node_data['name'],
'IO_group': node_data['IO_group_id'],
'WWNN': node_data['WWNN'],
'status': node_data['status'],
'WWPN': [],
'protocol': None,
'iscsi_name': node_data['iscsi_name'],
'config_node': node_data['config_node'],
'ipv4': [],
'ipv6': [],
}
if node['status'] == 'online':
self._storage_nodes[node['id']] = node
except KeyError:
self._handle_keyerror('lsnode', header)
def _get_vdisk_attributes(self, vdisk_name):
"""Return vdisk attributes
@ -574,70 +540,6 @@ class FlashSystemDriver(san.SanDriver):
return return_data
def _get_vdisk_map_properties(
self, connector, lun_id, vdisk_name, vdisk_id, vdisk_params):
"""Get the map properties of vdisk."""
LOG.debug(
'enter: _get_vdisk_map_properties: vdisk '
'%(vdisk_name)s.', {'vdisk_name': vdisk_name})
preferred_node = '0'
IO_group = '0'
# Get preferred node and other nodes in I/O group
preferred_node_entry = None
io_group_nodes = []
for k, node in self._storage_nodes.items():
if vdisk_params['protocol'] != node['protocol']:
continue
if node['id'] == preferred_node:
preferred_node_entry = node
if node['IO_group'] == IO_group:
io_group_nodes.append(node)
if not io_group_nodes:
msg = (_('_get_vdisk_map_properties: No node found in '
'I/O group %(gid)s for volume %(vol)s.')
% {'gid': IO_group, 'vol': vdisk_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not preferred_node_entry and not vdisk_params['multipath']:
# Get 1st node in I/O group
preferred_node_entry = io_group_nodes[0]
LOG.warning(_LW('_get_vdisk_map_properties: Did not find a '
'preferred node for vdisk %s.'), vdisk_name)
properties = {}
properties['target_discovered'] = False
properties['target_lun'] = lun_id
properties['volume_id'] = vdisk_id
type_str = 'fibre_channel'
conn_wwpns = self._get_conn_fc_wwpns()
if not conn_wwpns:
msg = (_('_get_vdisk_map_properties: Could not get FC '
'connection information for the host-volume '
'connection. Is the host configured properly '
'for FC connections?'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
properties['target_wwn'] = conn_wwpns
if "zvm_fcp" in connector:
properties['zvm_fcp'] = connector['zvm_fcp']
properties['initiator_target_map'] = self._build_initiator_target_map(
connector['wwpns'], conn_wwpns)
LOG.debug(
'leave: _get_vdisk_map_properties: vdisk '
'%(vdisk_name)s.', {'vdisk_name': vdisk_name})
return {'driver_volume_type': type_str, 'data': properties}
def _get_vdisk_params(self, type_id):
params = self._build_default_params()
if type_id:
@ -792,11 +694,11 @@ class FlashSystemDriver(san.SanDriver):
out, err = self._ssh(ssh_cmd, check_exit_code=False)
if err and err.startswith('CMMVC6071E'):
if not self.configuration.flashsystem_multihostmap_enabled:
msg = (_('flashsystem_multihostmap_enabled is set '
'to False, not allow multi host mapping. '
'CMMVC6071E The VDisk-to-host mapping '
'was not created because the VDisk is '
'already mapped to a host.'))
msg = _('flashsystem_multihostmap_enabled is set '
'to False, not allow multi host mapping. '
'CMMVC6071E The VDisk-to-host mapping '
'was not created because the VDisk is '
'already mapped to a host.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
@ -961,7 +863,7 @@ class FlashSystemDriver(san.SanDriver):
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes:
msg = (_('_update_volume_stats: Could not get storage pool data.'))
msg = _('_update_volume_stats: Could not get storage pool data.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
@ -1014,97 +916,6 @@ class FlashSystemDriver(san.SanDriver):
self._is_vdisk_copy_in_progress, vdisk_name)
timer.start(interval=self._check_lock_interval).wait()
def do_setup(self, ctxt):
"""Check that we have all configuration details from the storage."""
LOG.debug('enter: do_setup')
self._context = ctxt
# Get storage system name and id
ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes or not ('name' in attributes):
msg = (_('do_setup: Could not get system name.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self._system_name = attributes['name']
self._system_id = attributes['id']
# Validate value of open_access_enabled flag, for now only
# support when open_access_enabled is off
if not attributes or not ('open_access_enabled' in attributes) or (
attributes['open_access_enabled'] != 'off'):
msg = (_('do_setup: open_access_enabled is not off.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Validate that the array exists
pool = FLASHSYSTEM_VOLPOOL_NAME
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes or not ('status' in attributes) or (
attributes['status'] == 'offline'):
msg = (_('do_setup: Array does not exist or is offline.'))
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
# Get the FC names of the FlashSystem nodes
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
out.strip(), 'do_setup', ssh_cmd, out, err)
nodes = out.strip().splitlines()
self._assert_ssh_return(nodes, 'do_setup', ssh_cmd, out, err)
header = nodes.pop(0)
for node_line in nodes:
try:
node_data = self._get_hdr_dic(header, node_line, '!')
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
self._log_cli_output_error('do_setup', ssh_cmd, out, err)
node = {}
try:
node['id'] = node_data['id']
node['name'] = node_data['name']
node['IO_group'] = node_data['IO_group_id']
node['WWNN'] = node_data['WWNN']
node['status'] = node_data['status']
node['WWPN'] = []
node['protocol'] = None
if node['status'] == 'online':
self._storage_nodes[node['id']] = node
except KeyError:
self._handle_keyerror('lsnode', header)
# Get the WWPNs of the FlashSystem nodes
self._get_fc_wwpns()
# For each node, check what connection modes it supports. Delete any
# nodes that do not support any types (may be partially configured).
to_delete = []
for k, node in self._storage_nodes.items():
if not node['WWPN']:
to_delete.append(k)
for delkey in to_delete:
del self._storage_nodes[delkey]
# Make sure we have at least one node configured
self._driver_assert(
self._storage_nodes,
'do_setup: No configured nodes.')
self._protocol = node['protocol'] = 'FC'
# Set for vdisk synchronization
self._vdisk_copy_in_progress = set()
self._vdisk_copy_lock = threading.Lock()
self._check_lock_interval = 5
LOG.debug('leave: do_setup')
def check_for_setup_error(self):
"""Ensure that the flags are set properly."""
LOG.debug('enter: check_for_setup_error')
@ -1115,7 +926,7 @@ class FlashSystemDriver(san.SanDriver):
_('check_for_setup_error: Unable to determine system name.'))
raise exception.VolumeBackendAPIException(data=msg)
if self._system_id is None:
msg = (_('check_for_setup_error: Unable to determine system id.'))
msg = _('check_for_setup_error: Unable to determine system id.')
raise exception.VolumeBackendAPIException(data=msg)
required_flags = ['san_ip', 'san_ssh_port', 'san_login']
@ -1127,9 +938,9 @@ class FlashSystemDriver(san.SanDriver):
# Ensure that either password or keyfile were set
if not (self.configuration.san_password or
self.configuration.san_private_key):
msg = (_('check_for_setup_error: Password or SSH private key '
'is required for authentication: set either '
'san_password or san_private_key option.'))
msg = _('check_for_setup_error: Password or SSH private key '
'is required for authentication: set either '
'san_password or san_private_key option.')
raise exception.InvalidInput(reason=msg)
params = self._build_default_params()
@ -1137,13 +948,6 @@ class FlashSystemDriver(san.SanDriver):
LOG.debug('leave: check_for_setup_error')
def validate_connector(self, connector):
"""Check connector."""
if 'FC' == self._protocol and 'wwpns' not in connector:
LOG.error(_LE('The connector does not contain the '
'required information: wwpns is missing'))
raise exception.InvalidConnectorException(missing='wwpns')
def create_volume(self, volume):
"""Create volume."""
vdisk_name = volume['name']
@ -1175,96 +979,6 @@ class FlashSystemDriver(san.SanDriver):
LOG.debug('leave: extend_volume: volume %s.', volume['name'])
@fczm_utils.AddFCZone
@utils.synchronized('flashsystem-init-conn', external=True)
def initialize_connection(self, volume, connector):
"""Perform the necessary work so that a FC connection can
be made.
To be able to create a FC connection from a given host to a
volume, we must:
1. Translate the given WWNN to a host name
2. Create new host on the storage system if it does not yet exist
3. Map the volume to the host if it is not already done
4. Return the connection information for relevant nodes (in the
proper I/O group)
"""
LOG.debug(
'enter: initialize_connection: volume %(vol)s with '
'connector %(conn)s.', {'vol': volume, 'conn': connector})
vdisk_name = volume['name']
vdisk_id = volume['id']
vdisk_params = self._get_vdisk_params(volume['volume_type_id'])
self._wait_vdisk_copy_completed(vdisk_name)
self._driver_assert(
self._is_vdisk_defined(vdisk_name),
(_('initialize_connection: vdisk %s is not defined.')
% vdisk_name))
lun_id = self._map_vdisk_to_host(vdisk_name, connector)
properties = {}
try:
properties = self._get_vdisk_map_properties(
connector, lun_id, vdisk_name, vdisk_id, vdisk_params)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
self.terminate_connection(volume, connector)
LOG.error(_LE('initialize_connection: Failed to collect '
'return properties for volume %(vol)s and '
'connector %(conn)s.'),
{'vol': volume, 'conn': connector})
LOG.debug(
'leave: initialize_connection:\n volume: %(vol)s\n connector '
'%(conn)s\n properties: %(prop)s.',
{'vol': volume,
'conn': connector,
'prop': properties})
return properties
@fczm_utils.RemoveFCZone
@utils.synchronized('flashsystem-term-conn', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after connection has been terminated.
When we clean up a terminated connection between a given connector
and volume, we:
1. Translate the given connector to a host name
2. Remove the volume-to-host mapping if it exists
3. Delete the host if it has no more mappings (hosts are created
automatically by this driver when mappings are created)
"""
LOG.debug(
'enter: terminate_connection: volume %(vol)s with '
'connector %(conn)s.',
{'vol': volume, 'conn': connector})
vdisk_name = volume['name']
self._wait_vdisk_copy_completed(vdisk_name)
self._unmap_vdisk_from_host(vdisk_name, connector)
properties = {}
conn_wwpns = self._get_conn_fc_wwpns()
properties['target_wwn'] = conn_wwpns
properties['initiator_target_map'] = self._build_initiator_target_map(
connector['wwpns'], conn_wwpns)
LOG.debug(
'leave: terminate_connection: volume %(vol)s with '
'connector %(conn)s.', {'vol': volume, 'conn': connector})
return {
'driver_volume_type': 'fibre_channel',
'data': properties
}
def create_snapshot(self, snapshot):
"""Create snapshot from volume."""
@ -1311,8 +1025,8 @@ class FlashSystemDriver(san.SanDriver):
'%(snap)s.', {'vol': volume['name'], 'snap': snapshot['name']})
if volume['size'] != snapshot['volume_size']:
msg = (_('create_volume_from_snapshot: Volume size is different '
'from snapshot based volume.'))
msg = _('create_volume_from_snapshot: Volume size is different '
'from snapshot based volume.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
@ -1339,8 +1053,8 @@ class FlashSystemDriver(san.SanDriver):
{'src': src_volume['name'], 'vol': volume['name']})
if src_volume['size'] != volume['size']:
msg = (_('create_cloned_volume: Source and destination '
'size differ.'))
msg = _('create_cloned_volume: Source and destination '
'size differ.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)

View File

@ -0,0 +1,379 @@
# Copyright 2015 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""
Volume driver for IBM FlashSystem storage systems with FC protocol.
Limitations:
1. Cinder driver only works when open_access_enabled=off.
"""
import random
import threading
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
import six
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils
import cinder.volume.driver
from cinder.volume.drivers.ibm import flashsystem_common as fscommon
from cinder.volume.drivers.san import san
from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
flashsystem_fc_opts = [
cfg.BoolOpt('flashsystem_multipath_enabled',
default=False,
help='Connect with multipath (FC only).'
'(Default is false.)')
]
CONF = cfg.CONF
CONF.register_opts(flashsystem_fc_opts)
class FlashSystemFCDriver(fscommon.FlashSystemDriver,
cinder.volume.driver.FibreChannelDriver):
"""IBM FlashSystem FC volume driver.
Version history:
1.0.0 - Initial driver
1.0.1 - Code clean up
1.0.2 - Add lock into vdisk map/unmap, connection
initialize/terminate
1.0.3 - Initial driver for iSCSI
1.0.4 - Split Flashsystem driver into common and FC
"""
VERSION = "1.0.4"
def __init__(self, *args, **kwargs):
super(FlashSystemFCDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(fscommon.flashsystem_opts)
self.configuration.append_config_values(flashsystem_fc_opts)
self.configuration.append_config_values(san.san_opts)
def _check_vdisk_params(self, params):
# Check that the requested protocol is enabled
if params['protocol'] != self._protocol:
msg = (_("Illegal value '%(prot)s' specified for "
"flashsystem_connection_protocol: "
"valid value(s) are %(enabled)s.")
% {'prot': params['protocol'],
'enabled': self._protocol})
raise exception.InvalidInput(reason=msg)
def _create_host(self, connector):
"""Create a new host on the storage system.
We create a host and associate it with the given connection
information.
"""
LOG.debug('enter: _create_host: host %s.', connector['host'])
rand_id = six.text_type(random.randint(0, 99999999)).zfill(8)
host_name = '%s-%s' % (self._connector_to_hostname_prefix(connector),
rand_id)
ports = []
if 'FC' == self._protocol and 'wwpns' in connector:
for wwpn in connector['wwpns']:
ports.append('-hbawwpn %s' % wwpn)
self._driver_assert(ports,
(_('_create_host: No connector ports.')))
port1 = ports.pop(0)
arg_name, arg_val = port1.split()
ssh_cmd = ['svctask', 'mkhost', '-force', arg_name, arg_val, '-name',
'"%s"' % host_name]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return('successfully created' in out,
'_create_host', ssh_cmd, out, err)
for port in ports:
arg_name, arg_val = port.split()
ssh_cmd = ['svctask', 'addhostport', '-force',
arg_name, arg_val, host_name]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
(not out.strip()),
'_create_host', ssh_cmd, out, err)
LOG.debug(
'leave: _create_host: host %(host)s - %(host_name)s.',
{'host': connector['host'], 'host_name': host_name})
return host_name
def _find_host_exhaustive(self, connector, hosts):
for host in hosts:
ssh_cmd = ['svcinfo', 'lshost', '-delim', '!', host]
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
out.strip(),
'_find_host_exhaustive', ssh_cmd, out, err)
for attr_line in out.split('\n'):
# If '!' not found, return the string and two empty strings
attr_name, foo, attr_val = attr_line.partition('!')
if (attr_name == 'WWPN' and
'wwpns' in connector and attr_val.lower() in
map(str.lower, map(str, connector['wwpns']))):
return host
return None
def _get_conn_fc_wwpns(self):
wwpns = []
cmd = ['svcinfo', 'lsportfc']
generator = self._port_conf_generator(cmd)
header = next(generator, None)
if not header:
return wwpns
for port_data in generator:
try:
if port_data['status'] == 'active':
wwpns.append(port_data['WWPN'])
except KeyError:
self._handle_keyerror('lsportfc', header)
return wwpns
def _get_fc_wwpns(self):
for key in self._storage_nodes:
node = self._storage_nodes[key]
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!', node['id']]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
wwpns = set(node['WWPN'])
for i, s in zip(attributes['port_id'], attributes['port_status']):
if 'unconfigured' != s:
wwpns.add(i)
node['WWPN'] = list(wwpns)
LOG.info(_LI('WWPN on node %(node)s: %(wwpn)s.'),
{'node': node['id'], 'wwpn': node['WWPN']})
def _get_vdisk_map_properties(
self, connector, lun_id, vdisk_name, vdisk_id, vdisk_params):
"""Get the map properties of vdisk."""
LOG.debug(
'enter: _get_vdisk_map_properties: vdisk '
'%(vdisk_name)s.', {'vdisk_name': vdisk_name})
preferred_node = '0'
IO_group = '0'
# Get preferred node and other nodes in I/O group
preferred_node_entry = None
io_group_nodes = []
for k, node in self._storage_nodes.items():
if vdisk_params['protocol'] != node['protocol']:
continue
if node['id'] == preferred_node:
preferred_node_entry = node
if node['IO_group'] == IO_group:
io_group_nodes.append(node)
if not io_group_nodes:
msg = (_('_get_vdisk_map_properties: No node found in '
'I/O group %(gid)s for volume %(vol)s.')
% {'gid': IO_group, 'vol': vdisk_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not preferred_node_entry and not vdisk_params['multipath']:
# Get 1st node in I/O group
preferred_node_entry = io_group_nodes[0]
LOG.warning(_LW('_get_vdisk_map_properties: Did not find a '
'preferred node for vdisk %s.'), vdisk_name)
properties = {}
properties['target_discovered'] = False
properties['target_lun'] = lun_id
properties['volume_id'] = vdisk_id
type_str = 'fibre_channel'
conn_wwpns = self._get_conn_fc_wwpns()
if not conn_wwpns:
msg = _('_get_vdisk_map_properties: Could not get FC '
'connection information for the host-volume '
'connection. Is the host configured properly '
'for FC connections?')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
properties['target_wwn'] = conn_wwpns
if "zvm_fcp" in connector:
properties['zvm_fcp'] = connector['zvm_fcp']
properties['initiator_target_map'] = self._build_initiator_target_map(
connector['wwpns'], conn_wwpns)
LOG.debug(
'leave: _get_vdisk_map_properties: vdisk '
'%(vdisk_name)s.', {'vdisk_name': vdisk_name})
return {'driver_volume_type': type_str, 'data': properties}
@fczm_utils.AddFCZone
@utils.synchronized('flashsystem-init-conn', external=True)
def initialize_connection(self, volume, connector):
"""Perform work so that an FC connection can be made.
To be able to create a FC connection from a given host to a
volume, we must:
1. Translate the given WWNN to a host name
2. Create new host on the storage system if it does not yet exist
3. Map the volume to the host if it is not already done
4. Return the connection information for relevant nodes (in the
proper I/O group)
"""
LOG.debug(
'enter: initialize_connection: volume %(vol)s with '
'connector %(conn)s.', {'vol': volume, 'conn': connector})
vdisk_name = volume['name']
vdisk_id = volume['id']
vdisk_params = self._get_vdisk_params(volume['volume_type_id'])
# TODO(edwin): might fix it after vdisk copy function is
# ready in FlashSystem thin-provision layer. As this validation
# is to check the vdisk which is in copying, at present in firmware
# level vdisk doesn't allow to map host which it is copy. New
# vdisk clone and snapshot function will cover it. After that the
# _wait_vdisk_copy_completed need some modification.
self._wait_vdisk_copy_completed(vdisk_name)
self._driver_assert(
self._is_vdisk_defined(vdisk_name),
(_('initialize_connection: vdisk %s is not defined.')
% vdisk_name))
lun_id = self._map_vdisk_to_host(vdisk_name, connector)
properties = {}
try:
properties = self._get_vdisk_map_properties(
connector, lun_id, vdisk_name, vdisk_id, vdisk_params)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
self.terminate_connection(volume, connector)
LOG.error(_LE('initialize_connection: Failed to collect '
'return properties for volume %(vol)s and '
'connector %(conn)s.'),
{'vol': volume, 'conn': connector})
LOG.debug(
'leave: initialize_connection:\n volume: %(vol)s\n connector '
'%(conn)s\n properties: %(prop)s.',
{'vol': volume,
'conn': connector,
'prop': properties})
return properties
@fczm_utils.RemoveFCZone
@utils.synchronized('flashsystem-term-conn', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after connection has been terminated.
When we clean up a terminated connection between a given connector
and volume, we:
1. Translate the given connector to a host name
2. Remove the volume-to-host mapping if it exists
3. Delete the host if it has no more mappings (hosts are created
automatically by this driver when mappings are created)
"""
LOG.debug(
'enter: terminate_connection: volume %(vol)s with '
'connector %(conn)s.',
{'vol': volume, 'conn': connector})
vdisk_name = volume['name']
self._wait_vdisk_copy_completed(vdisk_name)
self._unmap_vdisk_from_host(vdisk_name, connector)
properties = {}
conn_wwpns = self._get_conn_fc_wwpns()
properties['target_wwn'] = conn_wwpns
# TODO(edwin): add judgement here. No initiator_target_map within
# properties need if no more I/T connection. Otherwise the FCZone
# manager will remove the zoning between I/T.
properties['initiator_target_map'] = self._build_initiator_target_map(
connector['wwpns'], conn_wwpns)
LOG.debug(
'leave: terminate_connection: volume %(vol)s with '
'connector %(conn)s.', {'vol': volume, 'conn': connector})
return {
'driver_volume_type': 'fibre_channel',
'data': properties
}
def do_setup(self, ctxt):
"""Check that we have all configuration details from the storage."""
self._context = ctxt
# Get data of configured node
self._get_node_data()
# Get the WWPNs of the FlashSystem nodes
self._get_fc_wwpns()
# For each node, check what connection modes it supports. Delete any
# nodes that do not support any types (may be partially configured).
to_delete = []
for k, node in self._storage_nodes.items():
if not node['WWPN']:
to_delete.append(k)
for delkey in to_delete:
del self._storage_nodes[delkey]
# Make sure we have at least one node configured
self._driver_assert(self._storage_nodes,
'do_setup: No configured nodes.')
self._protocol = node['protocol'] = 'FC'
# Set for vdisk synchronization
self._vdisk_copy_in_progress = set()
self._vdisk_copy_lock = threading.Lock()
self._check_lock_interval = 5
def validate_connector(self, connector):
"""Check connector."""
if 'FC' == self._protocol and 'wwpns' not in connector:
msg = _LE('The connector does not contain the '
'required information: wwpns is missing')
LOG.error(msg)
raise exception.InvalidConnectorException(missing='wwpns')

View File

@ -33,8 +33,9 @@ import six
from cinder import exception
from cinder.i18n import _, _LE, _LW
from cinder import utils
import cinder.volume.driver
from cinder.volume.drivers.ibm import flashsystem as fscommon
from cinder.volume.drivers.ibm import flashsystem_common as fscommon
from cinder.volume.drivers.san import san
LOG = logging.getLogger(__name__)
@ -52,14 +53,19 @@ CONF.register_opts(flashsystem_iscsi_opts)
class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
cinder.volume.driver.ISCSIDriver):
"""IBM FlashSystem iSCSI volume driver
"""IBM FlashSystem iSCSI volume driver.
Version history:
1.0.2 - Initial driver for iSCSI
1.0.0 - Initial driver
1.0.1 - Code clean up
1.0.2 - Add lock into vdisk map/unmap, connection
initialize/terminate
1.0.3 - Initial driver for iSCSI
1.0.4 - Split Flashsystem driver into common and FC
"""
VERSION = "1.0.2"
VERSION = "1.0.4"
def __init__(self, *args, **kwargs):
super(FlashSystemISCSIDriver, self).__init__(*args, **kwargs)
@ -171,11 +177,11 @@ class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not preferred_node_entry and not vdisk_params['multipath']:
if not preferred_node_entry:
# Get 1st node in I/O group
preferred_node_entry = io_group_nodes[0]
LOG.warning(_LW('_get_vdisk_map_properties: Did not find a '
'preferred node for vdisk %s.'), vdisk_name)
'preferred node for vdisk %s.'), vdisk_name)
properties = {
'target_discovered': False,
'target_lun': lun_id,
@ -197,6 +203,7 @@ class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
return {'driver_volume_type': type_str, 'data': properties}
@utils.synchronized('flashsystem-init-conn', external=True)
def initialize_connection(self, volume, connector):
"""Perform work so that an iSCSI connection can be made.
@ -222,7 +229,7 @@ class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
self._driver_assert(
self._is_vdisk_defined(vdisk_name),
(_('initialize_connection: vdisk %s is not defined.')
(_('vdisk %s is not defined.')
% vdisk_name))
lun_id = self._map_vdisk_to_host(vdisk_name, connector)
@ -247,6 +254,7 @@ class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
return properties
@utils.synchronized('flashsystem-term-conn', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after connection has been terminated.
@ -350,92 +358,16 @@ class FlashSystemISCSIDriver(fscommon.FlashSystemDriver,
LOG.debug('leave: do_setup')
def _get_node_data(self):
"""Get and verify node configuration."""
LOG.debug('enter: _get_node_data')
# Get storage system name and id
ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes or not ('name' in attributes):
msg = _('Could not get system name.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self._system_name = attributes['name']
self._system_id = attributes['id']
# Validate value of open_access_enabled flag, for now only
# support when open_access_enabled is off
if not attributes or not ('open_access_enabled' in attributes) or (
attributes['open_access_enabled'] != 'off'):
msg = _('open_access_enabled is not off.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Validate that the array exists
pool = fscommon.FLASHSYSTEM_VOLPOOL_NAME
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
attributes = self._execute_command_and_parse_attributes(ssh_cmd)
if not attributes:
LOG.debug('_get_node_data: lssystem attributes:', attributes)
msg = _('Unable to parse attributes')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if not ('status' in attributes) or (
attributes['status'] == 'offline'):
msg = (_('Array does not exist or is offline. '
'Current status of array is %ds.')
% attributes['status'])
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
# Get the iSCSI names of the FlashSystem nodes
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
out, err = self._ssh(ssh_cmd)
self._assert_ssh_return(
out.strip(), '_get_config_data', ssh_cmd, out, err)
nodes = out.strip().splitlines()
self._assert_ssh_return(nodes, '_get_node_data', ssh_cmd, out, err)
header = nodes.pop(0)
for node_line in nodes:
try:
node_data = self._get_hdr_dic(header, node_line, '!')
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
self._log_cli_output_error('_get_node_data',
ssh_cmd, out, err)
try:
node = {
'id': node_data['id'],
'name': node_data['name'],
'IO_group': node_data['IO_group_id'],
'WWNN': node_data['WWNN'],
'status': node_data['status'],
'WWPN': [],
'protocol': None,
'iscsi_name': node_data['iscsi_name'],
'config_node': node_data['config_node'],
'ipv4': [],
'ipv6': [],
}
if node['status'] == 'online':
self._storage_nodes[node['id']] = node
except KeyError:
self._handle_keyerror('lsnode', header)
LOG.debug('leave: _get_iscsi_ip_addrs')
def _build_default_params(self):
protocol = self.configuration.flashsystem_connection_protocol
if protocol.lower() == 'iscsi':
protocol = 'iSCSI'
return {'protocol': protocol,
'multipath': self.configuration.flashsystem_multipath_enabled,
'iscsi_ip': self.configuration.iscsi_ip_address,
'iscsi_port': self.configuration.iscsi_port,
'iscsi_ported': self.configuration.flashsystem_iscsi_portid}
return {
'protocol': protocol,
'iscsi_ip': self.configuration.iscsi_ip_address,
'iscsi_port': self.configuration.iscsi_port,
'iscsi_ported': self.configuration.flashsystem_iscsi_portid,
}
def validate_connector(self, connector):
"""Check connector for enabled protocol."""