Merge "Storwize driver cleanup"

This commit is contained in:
Jenkins 2014-01-26 08:43:16 +00:00 committed by Gerrit Code Review
commit 3923e5000c
9 changed files with 2030 additions and 2261 deletions

View File

@ -31,7 +31,7 @@ SOLARIS_MODULE = "cinder.volume.drivers.san.solaris.SolarisISCSIDriver"
LEFTHAND_MODULE = "cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver"
NFS_MODULE = "cinder.volume.drivers.nfs.NfsDriver"
SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFireDriver"
STORWIZE_SVC_MODULE = "cinder.volume.drivers.storwize_svc.StorwizeSVCDriver"
STORWIZE_MODULE = "cinder.volume.drivers.ibm.storwize_svc.StorwizeSVCDriver"
WINDOWS_MODULE = "cinder.volume.drivers.windows.windows.WindowsDriver"
XIV_DS8K_MODULE = "cinder.volume.drivers.xiv_ds8k.XIVDS8KDriver"
ZADARA_MODULE = "cinder.volume.drivers.zadara.ZadaraVPSAISCSIDriver"
@ -133,11 +133,16 @@ class VolumeDriverCompatibility(test.TestCase):
def test_storwize_svc_old(self):
self._load_driver('cinder.volume.storwize_svc.StorwizeSVCDriver')
self.assertEqual(self._driver_module_name(), STORWIZE_SVC_MODULE)
self.assertEqual(self._driver_module_name(), STORWIZE_MODULE)
def test_storwize_svc_old2(self):
self._load_driver('cinder.volume.drivers.storwize_svc.'
'StorwizeSVCDriver')
self.assertEqual(self._driver_module_name(), STORWIZE_MODULE)
def test_storwize_svc_new(self):
self._load_driver(STORWIZE_SVC_MODULE)
self.assertEqual(self._driver_module_name(), STORWIZE_SVC_MODULE)
self._load_driver(STORWIZE_MODULE)
self.assertEqual(self._driver_module_name(), STORWIZE_MODULE)
def test_windows_old(self):
self._load_driver('cinder.volume.windows.WindowsDriver')

View File

@ -18,7 +18,7 @@
Tests for the IBM Storwize family and SVC volume driver.
"""
import mock
import random
import re
@ -31,11 +31,10 @@ from cinder import test
from cinder import units
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers import storwize_svc
from cinder.volume.drivers.ibm import storwize_svc
from cinder.volume.drivers.ibm.storwize_svc import ssh
from cinder.volume import volume_types
from eventlet import greenthread
LOG = logging.getLogger(__name__)
@ -43,7 +42,7 @@ class StorwizeSVCFakeDB:
def __init__(self):
self.volume = None
def volume_get(self, context, vol_id):
def volume_get(self, ctxt, vol_id):
return self.volume
def volume_set(self, vol):
@ -168,9 +167,10 @@ class StorwizeSVCManagementSimulator:
return self._errors['CMMVC5903E']
# Find an unused ID
def _find_unused_id(self, d):
@staticmethod
def _find_unused_id(d):
ids = []
for k, v in d.iteritems():
for v in d.itervalues():
ids.append(int(v['id']))
ids.sort()
for index, n in enumerate(ids):
@ -179,13 +179,15 @@ class StorwizeSVCManagementSimulator:
return str(len(ids))
# Check if name is valid
def _is_invalid_name(self, name):
if re.match("^[a-zA-Z_][\w ._-]*$", name):
@staticmethod
def _is_invalid_name(name):
if re.match(r'^[a-zA-Z_][\w ._-]*$', name):
return False
return True
# Convert argument string to dictionary
def _cmd_to_dict(self, arg_list):
@staticmethod
def _cmd_to_dict(arg_list):
no_param_args = [
'autodelete',
'bytes',
@ -260,7 +262,8 @@ class StorwizeSVCManagementSimulator:
ret['obj'] = arg_list[i]
return ret
def _print_info_cmd(self, rows, delim=' ', nohdr=False, **kwargs):
@staticmethod
def _print_info_cmd(rows, delim=' ', nohdr=False, **kwargs):
"""Generic function for printing information."""
if nohdr:
del rows[0]
@ -269,7 +272,8 @@ class StorwizeSVCManagementSimulator:
rows[index] = delim.join(rows[index])
return ('%s' % '\n'.join(rows), '')
def _print_info_obj_cmd(self, header, row, delim=' ', nohdr=False):
@staticmethod
def _print_info_obj_cmd(header, row, delim=' ', nohdr=False):
"""Generic function for printing information for a specific object."""
objrows = []
for idx, val in enumerate(header):
@ -282,7 +286,8 @@ class StorwizeSVCManagementSimulator:
objrows[index] = delim.join(objrows[index])
return ('%s' % '\n'.join(objrows), '')
def _convert_bytes_units(self, bytestr):
@staticmethod
def _convert_bytes_units(bytestr):
num = int(bytestr)
unit_array = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
unit_index = 0
@ -293,7 +298,8 @@ class StorwizeSVCManagementSimulator:
return '%d%s' % (num, unit_array[unit_index])
def _convert_units_bytes(self, num, unit):
@staticmethod
def _convert_units_bytes(num, unit):
unit_array = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
unit_index = 0
@ -489,9 +495,9 @@ port_speed!N/A
target_wwpn = kwargs['wwpn'] if 'wwpn' in kwargs else None
host_infos = []
for hk, hv in self._hosts_list.iteritems():
for hv in self._hosts_list.itervalues():
if not host_name or hv['host_name'].startswith(host_name):
for mk, mv in self._mappings_list.iteritems():
for mv in self._mappings_list.itervalues():
if mv['host'] == hv['host_name']:
if not target_wwpn or target_wwpn in hv['wwpns']:
host_infos.append(hv)
@ -603,10 +609,10 @@ port_speed!N/A
return self._errors['CMMVC5753E']
if not force:
for k, mapping in self._mappings_list.iteritems():
for mapping in self._mappings_list.itervalues():
if mapping['vol'] == vol_name:
return self._errors['CMMVC5840E']
for k, fcmap in self._fcmappings_list.iteritems():
for fcmap in self._fcmappings_list.itervalues():
if ((fcmap['source'] == vol_name) or
(fcmap['target'] == vol_name)):
return self._errors['CMMVC5840E']
@ -638,7 +644,7 @@ port_speed!N/A
'fc_name': '',
'fc_map_count': '0',
}
for k, fcmap in self._fcmappings_list.iteritems():
for fcmap in self._fcmappings_list.itervalues():
if ((fcmap['source'] == vol_name) or
(fcmap['target'] == vol_name)):
ret_vals['fc_id'] = fcmap['id']
@ -655,7 +661,7 @@ port_speed!N/A
'RC_name', 'vdisk_UID', 'fc_map_count', 'copy_count',
'fast_write_state', 'se_copy_count', 'RC_change'])
for k, vol in self._volumes_list.iteritems():
for vol in self._volumes_list.itervalues():
if (('filtervalue' not in kwargs) or
(kwargs['filtervalue'] == 'name=' + vol['name'])):
fcmap_info = self._get_fcmap_info(vol['name'])
@ -694,14 +700,7 @@ port_speed!N/A
rows.append(['IO_group_id', vol['IO_group_id']])
rows.append(['IO_group_name', vol['IO_group_name']])
rows.append(['status', 'online'])
rows.append(['mdisk_grp_id', '0'])
if 'mdisk_grp_name' in vol:
mdisk_grp_name = vol['mdisk_grp_name']
else:
mdisk_grp_name = self._flags['storwize_svc_volpool_name']
rows.append(['mdisk_grp_name', mdisk_grp_name])
rows.append(['capacity', cap])
rows.append(['type', 'striped'])
rows.append(['formatted', 'no'])
rows.append(['mdisk_id', ''])
rows.append(['mdisk_name', ''])
@ -728,14 +727,22 @@ port_speed!N/A
rows.append(['se_copy_count', '0'])
rows.append(['mirror_write_priority', 'latency'])
rows.append(['RC_change', 'no'])
rows.append(['used_capacity', cap_u])
rows.append(['real_capacity', cap_r])
rows.append(['free_capacity', cap_f])
rows.append(['autoexpand', vol['autoexpand']])
rows.append(['warning', vol['warning']])
rows.append(['grainsize', vol['grainsize']])
rows.append(['easy_tier', vol['easy_tier']])
rows.append(['compressed_copy', vol['compressed_copy']])
for copy in vol['copies'].itervalues():
rows.append(['copy_id', copy['id']])
rows.append(['status', copy['status']])
rows.append(['primary', copy['primary']])
rows.append(['mdisk_grp_id', copy['mdisk_grp_id']])
rows.append(['mdisk_grp_name', copy['mdisk_grp_name']])
rows.append(['type', 'striped'])
rows.append(['used_capacity', cap_u])
rows.append(['real_capacity', cap_r])
rows.append(['free_capacity', cap_f])
rows.append(['easy_tier', copy['easy_tier']])
rows.append(['compressed_copy', copy['compressed_copy']])
rows.append(['autoexpand', vol['autoexpand']])
rows.append(['warning', vol['warning']])
rows.append(['grainsize', vol['grainsize']])
if 'nohdr' in kwargs:
for index in range(len(rows)):
@ -769,7 +776,7 @@ port_speed!N/A
host_info[added_key].append(added_val)
for k, v in self._hosts_list.iteritems():
for v in self._hosts_list.itervalues():
if v['id'] == host_info['id']:
continue
for port in v[added_key]:
@ -842,7 +849,7 @@ port_speed!N/A
if host_name not in self._hosts_list:
return self._errors['CMMVC5753E']
for k, v in self._mappings_list.iteritems():
for v in self._mappings_list.itervalues():
if (v['host'] == host_name):
return self._errors['CMMVC5871E']
@ -856,7 +863,7 @@ port_speed!N/A
rows.append(['id', 'name', 'port_count', 'iogrp_count', 'status'])
found = False
for k, host in self._hosts_list.iteritems():
for host in self._hosts_list.itervalues():
filterstr = 'name=' + host['host_name']
if (('filtervalue' not in kwargs) or
(kwargs['filtervalue'] == filterstr)):
@ -907,7 +914,7 @@ port_speed!N/A
rows.append(['type', 'id', 'name', 'iscsi_auth_method',
'iscsi_chap_secret'])
for k, host in self._hosts_list.iteritems():
for host in self._hosts_list.itervalues():
method = 'none'
secret = ''
if 'chapsecret' in host:
@ -943,12 +950,12 @@ port_speed!N/A
if mapping_info['vol'] in self._mappings_list:
return self._errors['CMMVC6071E']
for k, v in self._mappings_list.iteritems():
for v in self._mappings_list.itervalues():
if ((v['host'] == mapping_info['host']) and
(v['lun'] == mapping_info['lun'])):
return self._errors['CMMVC5879E']
for k, v in self._mappings_list.iteritems():
for v in self._mappings_list.itervalues():
if (v['lun'] == mapping_info['lun']) and ('force' not in kwargs):
return self._errors['CMMVC6071E']
@ -967,7 +974,7 @@ port_speed!N/A
vol = kwargs['obj'].strip('\'\'')
mapping_ids = []
for k, v in self._mappings_list.iteritems():
for v in self._mappings_list.itervalues():
if v['vol'] == vol:
mapping_ids.append(v['id'])
if not mapping_ids:
@ -985,9 +992,6 @@ port_speed!N/A
# List information about host->vdisk mappings
def _cmd_lshostvdiskmap(self, **kwargs):
index = 1
no_hdr = 0
delimeter = ''
host_name = kwargs['obj']
if host_name not in self._hosts_list:
@ -997,7 +1001,7 @@ port_speed!N/A
rows.append(['id', 'name', 'SCSI_id', 'vdisk_id', 'vdisk_name',
'vdisk_UID'])
for k, mapping in self._mappings_list.iteritems():
for mapping in self._mappings_list.itervalues():
if (host_name == '') or (mapping['host'] == host_name):
volume = self._volumes_list[mapping['vol']]
rows.append([mapping['id'], mapping['host'],
@ -1018,7 +1022,7 @@ port_speed!N/A
rows.append(['id name', 'SCSI_id', 'host_id', 'host_name', 'vdisk_UID',
'IO_group_id', 'IO_group_name'])
for k, mapping in self._mappings_list.iteritems():
for mapping in self._mappings_list.itervalues():
if (mapping['vol'] == vdisk_name):
mappings_found += 1
volume = self._volumes_list[mapping['vol']]
@ -1142,7 +1146,7 @@ port_speed!N/A
vdisk = kwargs['obj']
rows = []
rows.append(['id', 'name'])
for k, v in self._fcmappings_list.iteritems():
for v in self._fcmappings_list.itervalues():
if v['source'] == vdisk or v['target'] == vdisk:
rows.append([v['id'], v['name']])
return self._print_info_cmd(rows=rows, **kwargs)
@ -1284,7 +1288,7 @@ port_speed!N/A
'primary', 'mdisk_grp_id', 'mdisk_grp_name', 'capacity',
'type', 'se_copy', 'easy_tier', 'easy_tier_status',
'compressed_copy'])
for k, copy in vol['copies'].iteritems():
for copy in vol['copies'].itervalues():
rows.append([vol['id'], vol['name'], copy['id'],
copy['status'], copy['sync'], copy['primary'],
copy['mdisk_grp_id'], copy['mdisk_grp_name'],
@ -1332,10 +1336,6 @@ port_speed!N/A
return self._errors['CMMVC6353E']
del vol['copies'][copy_id]
copy_info = vol['copies'].values()[0]
for key in copy_info:
vol[key] = copy_info[key]
del vol['copies']
return ('', '')
def _cmd_chvdisk(self, **kwargs):
@ -1391,7 +1391,7 @@ port_speed!N/A
self._hosts_list[connector['host']] = host_info
def _host_in_list(self, host_name):
for k, v in self._hosts_list.iteritems():
for k in self._hosts_list:
if k.startswith(host_name):
return k
return None
@ -1502,7 +1502,7 @@ class StorwizeSVCFakeDriver(storwize_svc.StorwizeSVCDriver):
def set_fake_storage(self, fake):
self.fake_storage = fake
def _run_ssh(self, cmd, check_exit_code=True):
def _run_ssh(self, cmd, check_exit_code=True, attempts=1):
try:
LOG.debug(_('Run CLI command: %s') % cmd)
ret = self.fake_storage.execute_command(cmd, check_exit_code)
@ -1519,11 +1519,6 @@ class StorwizeSVCFakeDriver(storwize_svc.StorwizeSVCDriver):
return ret
class StorwizeSVCFakeSock:
def settimeout(self, time):
return
class StorwizeSVCDriverTestCase(test.TestCase):
def setUp(self):
super(StorwizeSVCDriverTestCase, self).setUp()
@ -1570,9 +1565,14 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.db = StorwizeSVCFakeDB()
self.driver.do_setup(None)
self.driver.check_for_setup_error()
self.stubs.Set(storwize_svc.time, 'sleep', lambda s: None)
self.stubs.Set(greenthread, 'sleep', lambda *x, **y: None)
self.stubs.Set(storwize_svc, 'CHECK_FCMAPPING_INTERVAL', 0)
self.sleeppatch = mock.patch('eventlet.greenthread.sleep')
self.sleeppatch.start()
self.driver._helpers.check_fcmapping_interval = 0
def tearDown(self):
if self.USESIM:
self.sleeppatch.stop()
super(StorwizeSVCDriverTestCase, self).tearDown()
def _set_flag(self, flag, value):
group = self.driver.configuration.config_group
@ -1584,7 +1584,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self._set_flag(k, v)
def _assert_vol_exists(self, name, exists):
is_vol_defined = self.driver._is_vdisk_defined(name)
is_vol_defined = self.driver._helpers.is_vdisk_defined(name)
self.assertEqual(is_vol_defined, exists)
def test_storwize_svc_connectivity(self):
@ -1696,17 +1696,11 @@ class StorwizeSVCDriverTestCase(test.TestCase):
volume['volume_type'] = type_ref
self.driver.create_volume(volume)
attrs = self.driver._get_vdisk_attributes(volume['name'])
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.driver.delete_volume(volume)
volume_types.destroy(ctxt, type_ref['id'])
return attrs
def _fail_prepare_fc_map(self, fc_map_id, source, target):
raise processutils.ProcessExecutionError(exit_code=1,
stdout='',
stderr='unit-test-fail',
cmd='prestartfcmap id')
def test_storwize_svc_snapshots(self):
vol1 = self._generate_vol_info(None, None)
self.driver.create_volume(vol1)
@ -1715,26 +1709,25 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Test timeout and volume cleanup
self._set_flag('storwize_svc_flashcopy_timeout', 1)
self.assertRaises(exception.InvalidSnapshot,
self.assertRaises(exception.VolumeDriverException,
self.driver.create_snapshot, snap1)
self._assert_vol_exists(snap1['name'], False)
self._reset_flags()
# Test prestartfcmap, startfcmap, and rmfcmap failing
orig = self.driver._call_prepare_fc_map
self.driver._call_prepare_fc_map = self._fail_prepare_fc_map
self.assertRaises(processutils.ProcessExecutionError,
self.driver.create_snapshot, snap1)
self.driver._call_prepare_fc_map = orig
# Test prestartfcmap failing
with mock.patch.object(ssh.StorwizeSSH, 'prestartfcmap') as prestart:
prestart.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap1)
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self.sim.error_injection('startfcmap', 'bad_id')
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap1)
self._assert_vol_exists(snap1['name'], False)
self.sim.error_injection('prestartfcmap', 'bad_id')
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap1)
self._assert_vol_exists(snap1['name'], False)
@ -1744,7 +1737,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Try to create a snapshot from an non-existing volume - should fail
snap_novol = self._generate_vol_info('undefined-vol', '12345')
self.assertRaises(exception.VolumeNotFound,
self.assertRaises(exception.VolumeDriverException,
self.driver.create_snapshot,
snap_novol)
@ -1765,23 +1758,22 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Try to create a volume from a non-existing snapshot
snap_novol = self._generate_vol_info('undefined-vol', '12345')
vol_novol = self._generate_vol_info(None, None)
self.assertRaises(exception.SnapshotNotFound,
self.assertRaises(exception.VolumeDriverException,
self.driver.create_volume_from_snapshot,
vol_novol,
snap_novol)
# Fail the snapshot
orig = self.driver._call_prepare_fc_map
self.driver._call_prepare_fc_map = self._fail_prepare_fc_map
self.assertRaises(processutils.ProcessExecutionError,
self.driver.create_volume_from_snapshot,
vol2, snap1)
self.driver._call_prepare_fc_map = orig
self._assert_vol_exists(vol2['name'], False)
with mock.patch.object(ssh.StorwizeSSH, 'prestartfcmap') as prestart:
prestart.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
vol2, snap1)
self._assert_vol_exists(vol2['name'], False)
# Try to create where source size != target size
vol2['size'] += 1
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.create_volume_from_snapshot,
vol2, snap1)
self._assert_vol_exists(vol2['name'], False)
@ -1795,7 +1787,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Try to clone where source size != target size
vol3['size'] += 1
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.create_cloned_volume,
vol3, vol2)
self._assert_vol_exists(vol3['name'], False)
@ -1828,14 +1820,14 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.remove_export(None, volume)
# Make sure volume attributes are as they should be
attributes = self.driver._get_vdisk_attributes(volume['name'])
attributes = self.driver._helpers.get_vdisk_attributes(volume['name'])
attr_size = float(attributes['capacity']) / units.GiB # bytes to GB
self.assertEqual(attr_size, float(volume['size']))
pool = self.driver.configuration.local_conf.storwize_svc_volpool_name
self.assertEqual(attributes['mdisk_grp_name'], pool)
# Try to create the volume again (should fail)
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume,
volume)
@ -1915,40 +1907,40 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.create_volume(volume1)
self._assert_vol_exists(volume1['name'], True)
self.assertRaises(exception.NoValidHost,
self.driver._connector_to_hostname_prefix,
self.assertRaises(exception.VolumeDriverException,
self.driver._helpers.create_host,
{'host': 12345})
# Add a a host first to make life interesting (this host and
# Add a host first to make life interesting (this host and
# conn['host'] should be translated to the same prefix, and the
# initiator should differentiate
tmpconn1 = {'initiator': u'unicode:initiator1.%s' % rand_id,
'ip': '10.10.10.10',
'host': u'unicode.foo}.bar{.baz-%s' % rand_id}
self.driver._create_host(tmpconn1)
self.driver._helpers.create_host(tmpconn1)
# Add a host with a different prefix
tmpconn2 = {'initiator': u'unicode:initiator2.%s' % rand_id,
'ip': '10.10.10.11',
'host': u'unicode.hello.world-%s' % rand_id}
self.driver._create_host(tmpconn2)
self.driver._helpers.create_host(tmpconn2)
conn = {'initiator': u'unicode:initiator3.%s' % rand_id,
'ip': '10.10.10.12',
'host': u'unicode.foo}.bar}.baz-%s' % rand_id}
self.driver.initialize_connection(volume1, conn)
host_name = self.driver._get_host_from_connector(conn)
host_name = self.driver._helpers.get_host_from_connector(conn)
self.assertIsNotNone(host_name)
self.driver.terminate_connection(volume1, conn)
host_name = self.driver._get_host_from_connector(conn)
host_name = self.driver._helpers.get_host_from_connector(conn)
self.assertIsNone(host_name)
self.driver.delete_volume(volume1)
# Clean up temporary hosts
for tmpconn in [tmpconn1, tmpconn2]:
host_name = self.driver._get_host_from_connector(tmpconn)
host_name = self.driver._helpers.get_host_from_connector(tmpconn)
self.assertIsNotNone(host_name)
self.driver._delete_host(host_name)
self.driver._helpers.delete_host(host_name)
def test_storwize_svc_validate_connector(self):
conn_neither = {'host': 'host'}
@ -1956,27 +1948,27 @@ class StorwizeSVCDriverTestCase(test.TestCase):
conn_fc = {'host': 'host', 'wwpns': 'bar'}
conn_both = {'host': 'host', 'initiator': 'foo', 'wwpns': 'bar'}
self.driver._enabled_protocols = set(['iSCSI'])
self.driver._state['enabled_protocols'] = set(['iSCSI'])
self.driver.validate_connector(conn_iscsi)
self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.validate_connector, conn_fc)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.validate_connector, conn_neither)
self.driver._enabled_protocols = set(['FC'])
self.driver._state['enabled_protocols'] = set(['FC'])
self.driver.validate_connector(conn_fc)
self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.validate_connector, conn_iscsi)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.validate_connector, conn_neither)
self.driver._enabled_protocols = set(['iSCSI', 'FC'])
self.driver._state['enabled_protocols'] = set(['iSCSI', 'FC'])
self.driver.validate_connector(conn_iscsi)
self.driver.validate_connector(conn_fc)
self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.validate_connector, conn_neither)
def test_storwize_svc_host_maps(self):
@ -2000,7 +1992,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Check case where no hosts exist
if self.USESIM:
ret = self.driver._get_host_from_connector(self._connector)
ret = self.driver._helpers.get_host_from_connector(
self._connector)
self.assertIsNone(ret)
# Make sure that the volumes have been created
@ -2014,7 +2007,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.initialize_connection(volume1, self._connector)
# Try to delete the 1st volume (should fail because it is mapped)
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_volume,
volume1)
@ -2028,7 +2021,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.terminate_connection(volume1, self._connector)
if self.USESIM:
ret = self.driver._get_host_from_connector(self._connector)
ret = self.driver._helpers.get_host_from_connector(
self._connector)
self.assertIsNone(ret)
# Check cases with no auth set for host
@ -2053,7 +2047,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
init_ret = self.driver.initialize_connection(volume1,
conn_na)
host_name = self.sim._host_in_list(conn_na['host'])
chap_ret = self.driver._get_chap_secret_for_host(host_name)
chap_ret = self.driver._helpers.get_chap_secret_for_host(
host_name)
if auth_enabled or host_exists == 'yes-auth':
self.assertIn('auth_password', init_ret['data'])
self.assertIsNotNone(chap_ret)
@ -2081,7 +2076,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
conn_no_exist = self._connector.copy()
conn_no_exist['initiator'] = 'i_dont_exist'
conn_no_exist['wwpns'] = ['0000000000000000']
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.terminate_connection,
volume1,
conn_no_exist)
@ -2099,7 +2094,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self._assert_vol_exists(volume1['name'], False)
# Make sure our host still exists
host_name = self.driver._get_host_from_connector(self._connector)
host_name = self.driver._helpers.get_host_from_connector(
self._connector)
self.assertIsNotNone(host_name)
# Remove the mapping from the 2nd volume. The host should
@ -2110,10 +2106,12 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# specified (see bug #1244257)
fake_conn = {'ip': '127.0.0.1', 'initiator': 'iqn.fake'}
self.driver.initialize_connection(volume2, self._connector)
host_name = self.driver._get_host_from_connector(self._connector)
host_name = self.driver._helpers.get_host_from_connector(
self._connector)
self.assertIsNotNone(host_name)
self.driver.terminate_connection(volume2, fake_conn)
host_name = self.driver._get_host_from_connector(self._connector)
host_name = self.driver._helpers.get_host_from_connector(
self._connector)
self.assertIsNone(host_name)
self.driver.delete_volume(volume2)
self._assert_vol_exists(volume2['name'], False)
@ -2124,7 +2122,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
# Check if our host still exists (it should not)
if self.USESIM:
ret = self.driver._get_host_from_connector(self._connector)
ret = self.driver._helpers.get_host_from_connector(self._connector)
self.assertIsNone(ret)
def test_storwize_svc_multi_host_maps(self):
@ -2181,7 +2179,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
if self.USESIM and False:
snap = self._generate_vol_info(master['name'], master['id'])
self.sim.error_injection('startfcmap', 'bad_id')
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap)
self._assert_vol_exists(snap['name'], False)
@ -2204,7 +2202,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
volfs = self._generate_vol_info(None, None)
self.sim.error_injection('startfcmap', 'bad_id')
self.sim.error_injection('lsfcmap', 'speed_up')
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
volfs, snap)
self._assert_vol_exists(volfs['name'], False)
@ -2231,7 +2229,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
clone = self._generate_vol_info(None, None)
self.sim.error_injection('startfcmap', 'bad_id')
self.sim.error_injection('lsfcmap', 'speed_up')
self.assertRaises(processutils.ProcessExecutionError,
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cloned_volume,
clone, volfs)
self._assert_vol_exists(clone['name'], False)
@ -2269,15 +2267,15 @@ class StorwizeSVCDriverTestCase(test.TestCase):
volume = self._generate_vol_info(None, None)
self.driver.db.volume_set(volume)
self.driver.create_volume(volume)
stats = self.driver.extend_volume(volume, '13')
attrs = self.driver._get_vdisk_attributes(volume['name'])
self.driver.extend_volume(volume, '13')
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
vol_size = int(attrs['capacity']) / units.GiB
self.assertAlmostEqual(vol_size, 13)
snap = self._generate_vol_info(volume['name'], volume['id'])
self.driver.create_snapshot(snap)
self._assert_vol_exists(snap['name'], True)
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.VolumeDriverException,
self.driver.extend_volume, volume, '16')
self.driver.delete_snapshot(snap)
@ -2301,40 +2299,44 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self._check_loc_info(cap, {'moved': False, 'model_update': None})
def test_storwize_svc_migrate_same_extent_size(self):
def _copy_info_exc(self, name):
raise Exception('should not be called')
self.stubs.Set(self.driver, '_get_vdisk_copy_info', _copy_info_exc)
self.driver.do_setup(None)
loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack2'
cap = {'location_info': loc, 'extent_size': '256'}
host = {'host': 'foo', 'capabilities': cap}
ctxt = context.get_admin_context()
volume = self._generate_vol_info(None, None)
volume['volume_type_id'] = None
self.driver.create_volume(volume)
self.driver.migrate_volume(ctxt, volume, host)
self.driver.delete_volume(volume)
# Make sure we don't call migrate_volume_vdiskcopy
with mock.patch.object(self.driver._helpers,
'migrate_volume_vdiskcopy') as migr_vdiskcopy:
migr_vdiskcopy.side_effect = KeyError
self.driver.do_setup(None)
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack2')
cap = {'location_info': loc, 'extent_size': '256'}
host = {'host': 'foo', 'capabilities': cap}
ctxt = context.get_admin_context()
volume = self._generate_vol_info(None, None)
volume['volume_type_id'] = None
self.driver.create_volume(volume)
self.driver.migrate_volume(ctxt, volume, host)
self.driver.delete_volume(volume)
def test_storwize_svc_migrate_diff_extent_size(self):
self.driver.do_setup(None)
loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack3'
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack3')
cap = {'location_info': loc, 'extent_size': '128'}
host = {'host': 'foo', 'capabilities': cap}
ctxt = context.get_admin_context()
volume = self._generate_vol_info(None, None)
volume['volume_type_id'] = None
self.driver.create_volume(volume)
self.assertNotEqual(cap['extent_size'], self.driver._extent_size)
self.assertNotEqual(cap['extent_size'],
self.driver._state['extent_size'])
self.driver.migrate_volume(ctxt, volume, host)
attrs = self.driver._get_vdisk_attributes(volume['name'])
self.assertEqual('openstack3', attrs['mdisk_grp_name'], 'migrate '
'with diff extent size failed')
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
print('AVISHAY ' + str(attrs))
self.assertIn('openstack3', attrs['mdisk_grp_name'])
self.driver.delete_volume(volume)
def test_storwize_svc_retype_no_copy(self):
self.driver.do_setup(None)
loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack'
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack')
cap = {'location_info': loc, 'extent_size': '128'}
self.driver._stats = {'location_info': loc}
host = {'host': 'foo', 'capabilities': cap}
@ -2345,7 +2347,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
diff, equel = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
new_type_ref['id'])
volume = self._generate_vol_info(None, None)
@ -2356,7 +2358,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.create_volume(volume)
self.driver.retype(ctxt, volume, new_type, diff, host)
attrs = self.driver._get_vdisk_attributes(volume['name'])
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertEqual('on', attrs['easy_tier'], 'Volume retype failed')
self.assertEqual('5', attrs['warning'], 'Volume retype failed')
self.assertEqual('off', attrs['autoexpand'], 'Volume retype failed')
@ -2364,7 +2366,8 @@ class StorwizeSVCDriverTestCase(test.TestCase):
def test_storwize_svc_retype_only_change_iogrp(self):
self.driver.do_setup(None)
loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack'
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack')
cap = {'location_info': loc, 'extent_size': '128'}
self.driver._stats = {'location_info': loc}
host = {'host': 'foo', 'capabilities': cap}
@ -2386,14 +2389,15 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.create_volume(volume)
self.driver.retype(ctxt, volume, new_type, diff, host)
attrs = self.driver._get_vdisk_attributes(volume['name'])
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertEqual('1', attrs['IO_group_id'], 'Volume retype '
'failed')
self.driver.delete_volume(volume)
def test_storwize_svc_retype_need_copy(self):
self.driver.do_setup(None)
loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack'
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack')
cap = {'location_info': loc, 'extent_size': '128'}
self.driver._stats = {'location_info': loc}
host = {'host': 'foo', 'capabilities': cap}
@ -2415,28 +2419,27 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver.create_volume(volume)
self.driver.retype(ctxt, volume, new_type, diff, host)
attrs = self.driver._get_vdisk_attributes(volume['name'])
self.assertEqual('no', attrs['compressed_copy'], 'Volume retype '
'failed')
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertEqual('no', attrs['compressed_copy'])
self.driver.delete_volume(volume)
def test_set_storage_code_level_success(self):
code_level = '7.2.0.0 (build 87.0.1311291000)'
res = self.driver._get_code_level(code_level)
self.assertEqual((7, 2, 0, 0), res, 'Get code level error')
res = self.driver._helpers.get_system_info()
self.assertEqual((7, 2, 0, 0), res['code_level'],
'Get code level error')
class CLIResponseTestCase(test.TestCase):
def test_empty(self):
self.assertEqual(0, len(storwize_svc.CLIResponse('')))
self.assertEqual(0, len(storwize_svc.CLIResponse(('', 'stderr'))))
self.assertEqual(0, len(ssh.CLIResponse('')))
self.assertEqual(0, len(ssh.CLIResponse(('', 'stderr'))))
def test_header(self):
raw = r'''id!name
1!node1
2!node2
'''
resp = storwize_svc.CLIResponse(raw, with_header=True)
resp = ssh.CLIResponse(raw, with_header=True)
self.assertEqual(2, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual('2', resp[1]['id'])
@ -2456,7 +2459,7 @@ age!40
home address!s3
home address!s4
'''
resp = storwize_svc.CLIResponse(raw, with_header=False)
resp = ssh.CLIResponse(raw, with_header=False)
self.assertEqual(list(resp.select('home address', 'name',
'home address')),
[('s1', 'Bill', 's1'), ('s2', 'Bill2', 's2'),
@ -2467,7 +2470,7 @@ home address!s4
1!node1!!500507680200C744!online
2!node2!!500507680200C745!online
'''
resp = storwize_svc.CLIResponse(raw)
resp = ssh.CLIResponse(raw)
self.assertEqual(2, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual('500507680200C744', resp[0]['WWNN'])
@ -2483,7 +2486,7 @@ port_id!500507680240C744
port_status!inactive
port_speed!8Gb
'''
resp = storwize_svc.CLIResponse(raw, with_header=False)
resp = ssh.CLIResponse(raw, with_header=False)
self.assertEqual(1, len(resp))
self.assertEqual('1', resp[0]['id'])
self.assertEqual(list(resp.select('port_id', 'port_status')),

View File

View File

@ -0,0 +1,638 @@
# Copyright 2013 IBM Corp.
# Copyright 2012 OpenStack Foundation
# 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 Storwize family and SVC storage systems.
Notes:
1. If you specify both a password and a key file, this driver will use the
key file only.
2. When using a key file for authentication, it is up to the user or
system administrator to store the private key in a safe manner.
3. The defaults for creating volumes are "-rsize 2% -autoexpand
-grainsize 256 -warning 0". These can be changed in the configuration
file or by using volume types(recommended only for advanced users).
Limitations:
1. The driver expects CLI output in English, error messages may be in a
localized format.
2. Clones and creating volumes from snapshots, where the source and target
are of different sizes, is not supported.
"""
from oslo.config import cfg
from cinder import context
from cinder import exception
from cinder.openstack.common import excutils
from cinder.openstack.common import log as logging
from cinder import units
from cinder.volume.drivers.ibm.storwize_svc import helpers as storwize_helpers
from cinder.volume.drivers.san import san
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
storwize_svc_opts = [
cfg.StrOpt('storwize_svc_volpool_name',
default='volpool',
help='Storage system storage pool for volumes'),
cfg.IntOpt('storwize_svc_vol_rsize',
default=2,
help='Storage system space-efficiency parameter for volumes '
'(percentage)'),
cfg.IntOpt('storwize_svc_vol_warning',
default=0,
help='Storage system threshold for volume capacity warnings '
'(percentage)'),
cfg.BoolOpt('storwize_svc_vol_autoexpand',
default=True,
help='Storage system autoexpand parameter for volumes '
'(True/False)'),
cfg.IntOpt('storwize_svc_vol_grainsize',
default=256,
help='Storage system grain size parameter for volumes '
'(32/64/128/256)'),
cfg.BoolOpt('storwize_svc_vol_compression',
default=False,
help='Storage system compression option for volumes'),
cfg.BoolOpt('storwize_svc_vol_easytier',
default=True,
help='Enable Easy Tier for volumes'),
cfg.IntOpt('storwize_svc_vol_iogrp',
default=0,
help='The I/O group in which to allocate volumes'),
cfg.IntOpt('storwize_svc_flashcopy_timeout',
default=120,
help='Maximum number of seconds to wait for FlashCopy to be '
'prepared. Maximum value is 600 seconds (10 minutes)'),
cfg.StrOpt('storwize_svc_connection_protocol',
default='iSCSI',
help='Connection protocol (iSCSI/FC)'),
cfg.BoolOpt('storwize_svc_iscsi_chap_enabled',
default=True,
help='Configure CHAP authentication for iSCSI connections '
'(Default: Enabled)'),
cfg.BoolOpt('storwize_svc_multipath_enabled',
default=False,
help='Connect with multipath (FC only; iSCSI multipath is '
'controlled by Nova)'),
cfg.BoolOpt('storwize_svc_multihostmap_enabled',
default=True,
help='Allows vdisk to multi host mapping'),
]
CONF = cfg.CONF
CONF.register_opts(storwize_svc_opts)
class StorwizeSVCDriver(san.SanDriver):
"""IBM Storwize V7000 and SVC iSCSI/FC volume driver.
Version history:
1.0 - Initial driver
1.1 - FC support, create_cloned_volume, volume type support,
get_volume_stats, minor bug fixes
1.2.0 - Added retype
1.2.1 - Code refactor, improved exception handling
"""
VERSION = "1.2.1"
def __init__(self, *args, **kwargs):
super(StorwizeSVCDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(storwize_svc_opts)
self._helpers = storwize_helpers.StorwizeHelpers(self._run_ssh)
self._state = {'storage_nodes': {},
'enabled_protocols': set(),
'compression_enabled': False,
'available_iogrps': [],
'system_name': None,
'system_id': None,
'extent_size': None,
'code_level': None,
}
def do_setup(self, ctxt):
"""Check that we have all configuration details from the storage."""
LOG.debug(_('enter: do_setup'))
# Get storage system name, id, and code level
self._state.update(self._helpers.get_system_info())
# Validate that the pool exists
pool = self.configuration.storwize_svc_volpool_name
try:
attributes = self._helpers.get_pool_attrs(pool)
except exception.VolumeBackendAPIException:
msg = _('Failed getting details for pool %s') % pool
raise exception.InvalidInput(reason=msg)
self._state['extent_size'] = attributes['extent_size']
# Check if compression is supported
self._state['compression_enabled'] = \
self._helpers.compression_enabled()
# Get the available I/O groups
self._state['available_iogrps'] = \
self._helpers.get_available_io_groups()
# Get the iSCSI and FC names of the Storwize/SVC nodes
self._state['storage_nodes'] = self._helpers.get_node_info()
# Add the iSCSI IP addresses and WWPNs to the storage node info
self._helpers.add_iscsi_ip_addrs(self._state['storage_nodes'])
self._helpers.add_fc_wwpns(self._state['storage_nodes'])
# 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._state['storage_nodes'].iteritems():
if ((len(node['ipv4']) or len(node['ipv6']))
and len(node['iscsi_name'])):
node['enabled_protocols'].append('iSCSI')
self._state['enabled_protocols'].add('iSCSI')
if len(node['WWPN']):
node['enabled_protocols'].append('FC')
self._state['enabled_protocols'].add('FC')
if not len(node['enabled_protocols']):
to_delete.append(k)
for delkey in to_delete:
del self._state['storage_nodes'][delkey]
# Make sure we have at least one node configured
if not len(self._state['storage_nodes']):
msg = _('do_setup: No configured nodes.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
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'))
# Check that we have the system ID information
if self._state['system_name'] is None:
exception_msg = (_('Unable to determine system name'))
raise exception.VolumeBackendAPIException(data=exception_msg)
if self._state['system_id'] is None:
exception_msg = (_('Unable to determine system id'))
raise exception.VolumeBackendAPIException(data=exception_msg)
if self._state['extent_size'] is None:
exception_msg = (_('Unable to determine pool extent size'))
raise exception.VolumeBackendAPIException(data=exception_msg)
required_flags = ['san_ip', 'san_ssh_port', 'san_login',
'storwize_svc_volpool_name']
for flag in required_flags:
if not self.configuration.safe_get(flag):
raise exception.InvalidInput(reason=_('%s is not set') % flag)
# Ensure that either password or keyfile were set
if not (self.configuration.san_password or
self.configuration.san_private_key):
raise exception.InvalidInput(
reason=_('Password or SSH private key is required for '
'authentication: set either san_password or '
'san_private_key option'))
# Check that flashcopy_timeout is not more than 10 minutes
flashcopy_timeout = self.configuration.storwize_svc_flashcopy_timeout
if not (flashcopy_timeout > 0 and flashcopy_timeout <= 600):
raise exception.InvalidInput(
reason=_('Illegal value %d specified for '
'storwize_svc_flashcopy_timeout: '
'valid values are between 0 and 600')
% flashcopy_timeout)
opts = self._helpers.build_default_opts(self.configuration)
self._helpers.check_vdisk_opts(self._state, opts)
LOG.debug(_('leave: check_for_setup_error'))
def ensure_export(self, ctxt, volume):
"""Check that the volume exists on the storage.
The system does not "export" volumes as a Linux iSCSI target does,
and therefore we just check that the volume exists on the storage.
"""
volume_defined = self._helpers.is_vdisk_defined(volume['name'])
if not volume_defined:
LOG.error(_('ensure_export: Volume %s not found on storage')
% volume['name'])
def create_export(self, ctxt, volume):
model_update = None
return model_update
def remove_export(self, ctxt, volume):
pass
def validate_connector(self, connector):
"""Check connector for at least one enabled protocol (iSCSI/FC)."""
valid = False
if ('iSCSI' in self._state['enabled_protocols'] and
'initiator' in connector):
valid = True
if 'FC' in self._state['enabled_protocols'] and 'wwpns' in connector:
valid = True
if not valid:
msg = (_('The connector does not contain the required '
'information.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def _get_vdisk_params(self, type_id, volume_type=None):
return self._helpers.get_vdisk_params(self.configuration, self._state,
type_id, volume_type=volume_type)
def initialize_connection(self, volume, connector):
"""Perform the necessary work so that an iSCSI/FC connection can
be made.
To be able to create an iSCSI/FC connection from a given host to a
volume, we must:
1. Translate the given iSCSI name or 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': str(volume),
'conn': str(connector)})
vol_opts = self._get_vdisk_params(volume['volume_type_id'])
host_name = connector['host']
volume_name = volume['name']
# Check if a host object is defined for this host name
host_name = self._helpers.get_host_from_connector(connector)
if host_name is None:
# Host does not exist - add a new host to Storwize/SVC
host_name = self._helpers.create_host(connector)
if vol_opts['protocol'] == 'iSCSI':
chap_secret = self._helpers.get_chap_secret_for_host(host_name)
chap_enabled = self.configuration.storwize_svc_iscsi_chap_enabled
if chap_enabled and chap_secret is None:
chap_secret = self._helpers.add_chap_secret_to_host(host_name)
elif not chap_enabled and chap_secret:
LOG.warning(_('CHAP secret exists for host but CHAP is '
'disabled'))
volume_attributes = self._helpers.get_vdisk_attributes(volume_name)
if volume_attributes is None:
msg = (_('initialize_connection: Failed to get attributes'
' for volume %s') % volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
multihostmap = self.configuration.storwize_svc_multihostmap_enabled
lun_id = self._helpers.map_vol_to_host(volume_name, host_name,
multihostmap)
try:
preferred_node = volume_attributes['preferred_node_id']
IO_group = volume_attributes['IO_group_id']
except KeyError as e:
LOG.error(_('Did not find expected column name in '
'lsvdisk: %s') % str(e))
msg = (_('initialize_connection: Missing volume '
'attribute for volume %s') % volume_name)
raise exception.VolumeBackendAPIException(data=msg)
try:
# Get preferred node and other nodes in I/O group
preferred_node_entry = None
io_group_nodes = []
for node in self._state['storage_nodes'].itervalues():
if vol_opts['protocol'] not in node['enabled_protocols']:
continue
if node['id'] == preferred_node:
preferred_node_entry = node
if node['IO_group'] == IO_group:
io_group_nodes.append(node)
if not len(io_group_nodes):
msg = (_('initialize_connection: No node found in '
'I/O group %(gid)s for volume %(vol)s') %
{'gid': IO_group, 'vol': volume_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not preferred_node_entry and not vol_opts['multipath']:
# Get 1st node in I/O group
preferred_node_entry = io_group_nodes[0]
LOG.warn(_('initialize_connection: Did not find a preferred '
'node for volume %s') % volume_name)
properties = {}
properties['target_discovered'] = False
properties['target_lun'] = lun_id
properties['volume_id'] = volume['id']
if vol_opts['protocol'] == 'iSCSI':
type_str = 'iscsi'
if len(preferred_node_entry['ipv4']):
ipaddr = preferred_node_entry['ipv4'][0]
else:
ipaddr = preferred_node_entry['ipv6'][0]
properties['target_portal'] = '%s:%s' % (ipaddr, '3260')
properties['target_iqn'] = preferred_node_entry['iscsi_name']
if chap_secret:
properties['auth_method'] = 'CHAP'
properties['auth_username'] = connector['initiator']
properties['auth_password'] = chap_secret
else:
type_str = 'fibre_channel'
conn_wwpns = self._helpers.get_conn_fc_wwpns(host_name)
if len(conn_wwpns) == 0:
msg = (_('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)
if not vol_opts['multipath']:
if preferred_node_entry['WWPN'] in conn_wwpns:
properties['target_wwn'] = preferred_node_entry['WWPN']
else:
properties['target_wwn'] = conn_wwpns[0]
else:
properties['target_wwn'] = conn_wwpns
except Exception:
with excutils.save_and_reraise_exception():
self.terminate_connection(volume, connector)
LOG.error(_('initialize_connection: Failed to collect return '
'properties for volume %(vol)s and connector '
'%(conn)s.\n') % {'vol': str(volume),
'conn': str(connector)})
LOG.debug(_('leave: initialize_connection:\n volume: %(vol)s\n '
'connector %(conn)s\n properties: %(prop)s')
% {'vol': str(volume),
'conn': str(connector),
'prop': str(properties)})
return {'driver_volume_type': type_str, 'data': properties, }
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after an iSCSI 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': str(volume),
'conn': str(connector)})
vol_name = volume['name']
if 'host' in connector:
host_name = self._helpers.get_host_from_connector(connector)
if host_name is None:
msg = (_('terminate_connection: Failed to get host name from'
' connector.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
else:
# See bug #1244257
host_name = None
self._helpers.unmap_vol_from_host(vol_name, host_name)
LOG.debug(_('leave: terminate_connection: volume %(vol)s with '
'connector %(conn)s') % {'vol': str(volume),
'conn': str(connector)})
def create_volume(self, volume):
opts = self._get_vdisk_params(volume['volume_type_id'])
pool = self.configuration.storwize_svc_volpool_name
return self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
def delete_volume(self, volume):
self._helpers.delete_vdisk(volume['name'], False)
def create_snapshot(self, snapshot):
ctxt = context.get_admin_context()
source_vol = self.db.volume_get(ctxt, snapshot['volume_id'])
opts = self._get_vdisk_params(source_vol['volume_type_id'])
self._helpers.create_copy(snapshot['volume_name'], snapshot['name'],
snapshot['volume_id'], self.configuration,
opts, False)
def delete_snapshot(self, snapshot):
self._helpers.delete_vdisk(snapshot['name'], False)
def create_volume_from_snapshot(self, volume, snapshot):
if volume['size'] != snapshot['volume_size']:
msg = (_('create_volume_from_snapshot: Source and destination '
'size differ.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
opts = self._get_vdisk_params(volume['volume_type_id'])
self._helpers.create_copy(snapshot['name'], volume['name'],
snapshot['id'], self.configuration,
opts, True)
def create_cloned_volume(self, tgt_volume, src_volume):
if src_volume['size'] != tgt_volume['size']:
msg = (_('create_cloned_volume: Source and destination '
'size differ.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
opts = self._get_vdisk_params(tgt_volume['volume_type_id'])
self._helpers.create_copy(src_volume['name'], tgt_volume['name'],
src_volume['id'], self.configuration,
opts, True)
def extend_volume(self, volume, new_size):
LOG.debug(_('enter: extend_volume: volume %s') % volume['id'])
ret = self._helpers.ensure_vdisk_no_fc_mappings(volume['name'],
allow_snaps=False)
if not ret:
msg = (_('extend_volume: Extending a volume with snapshots is not '
'supported.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
extend_amt = int(new_size) - volume['size']
self._helpers.extend_vdisk(volume['name'], extend_amt)
LOG.debug(_('leave: extend_volume: volume %s') % volume['id'])
def migrate_volume(self, ctxt, volume, host):
"""Migrate directly if source and dest are managed by same storage.
The method uses the migratevdisk method, which returns almost
immediately, if the source and target pools have the same extent_size.
Otherwise, it uses addvdiskcopy and rmvdiskcopy, which require waiting
for the copy operation to complete.
:param ctxt: Context
:param volume: A dictionary describing the volume to migrate
:param host: A dictionary describing the host to migrate to, where
host['host'] is its name, and host['capabilities'] is a
dictionary of its reported capabilities.
"""
LOG.debug(_('enter: migrate_volume: id=%(id)s, host=%(host)s') %
{'id': volume['id'], 'host': host['host']})
false_ret = (False, None)
dest_pool = self._helpers.can_migrate_to_host(host, self._state)
if dest_pool is None:
return false_ret
if 'extent_size' not in host['capabilities']:
return false_ret
if host['capabilities']['extent_size'] == self._state['extent_size']:
# If source and dest pools have the same extent size, migratevdisk
self._helpers.migrate_vdisk(volume['name'], dest_pool)
else:
# If source and dest pool extent size differ, add/delete vdisk copy
ctxt = context.get_admin_context()
if volume['volume_type_id'] is not None:
volume_type_id = volume['volume_type_id']
vol_type = volume_types.get_volume_type(ctxt, volume_type_id)
else:
vol_type = None
self._helpers.migrate_volume_vdiskcopy(volume['name'], dest_pool,
vol_type,
self._state,
self.configuration)
LOG.debug(_('leave: migrate_volume: id=%(id)s, host=%(host)s') %
{'id': volume['id'], 'host': host['host']})
return (True, None)
def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type.
Returns a boolean indicating whether the retype occurred.
:param ctxt: Context
:param volume: A dictionary describing the volume to migrate
:param new_type: A dictionary describing the volume type to convert to
:param diff: A dictionary with the difference between the two types
:param host: A dictionary describing the host to migrate to, where
host['host'] is its name, and host['capabilities'] is a
dictionary of its reported capabilities.
"""
LOG.debug(_('enter: retype: id=%(id)s, new_type=%(new_type)s,'
'diff=%(diff)s, host=%(host)s') % {'id': volume['id'],
'new_type': new_type,
'diff': diff,
'host': host})
ignore_keys = ['protocol', 'multipath']
no_copy_keys = ['warning', 'autoexpand', 'easytier', 'iogrp']
copy_keys = ['rsize', 'grainsize', 'compression']
all_keys = ignore_keys + no_copy_keys + copy_keys
old_opts = self._get_vdisk_params(volume['volume_type_id'])
new_opts = self._get_vdisk_params(new_type['id'],
volume_type=new_type)
vdisk_changes = []
need_copy = False
for key in all_keys:
if old_opts[key] != new_opts[key]:
if key in copy_keys:
need_copy = True
break
elif key in no_copy_keys:
vdisk_changes.append(key)
dest_location = host['capabilities'].get('location_info')
if self._stats['location_info'] != dest_location:
need_copy = True
if need_copy:
dest_pool = self._helpers.can_migrate_to_host(host, self._state)
if dest_pool is None:
return False
self._helpers.migrate_volume_vdiskcopy(volume['name'], dest_pool,
new_type,
self._state,
self.configuration)
else:
self._helpers.change_vdisk_options(volume['name'], vdisk_changes,
new_opts, self._state)
LOG.debug(_('exit: retype: ild=%(id)s, new_type=%(new_type)s,'
'diff=%(diff)s, host=%(host)s') % {'id': volume['id'],
'new_type': new_type,
'diff': diff,
'host': host['host']})
return True
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If we haven't gotten stats yet or 'refresh' is True,
run update the stats first.
"""
if not self._stats or refresh:
self._update_volume_stats()
return self._stats
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
LOG.debug(_("Updating volume stats"))
data = {}
data['vendor_name'] = 'IBM'
data['driver_version'] = self.VERSION
data['storage_protocol'] = list(self._state['enabled_protocols'])
data['total_capacity_gb'] = 0 # To be overwritten
data['free_capacity_gb'] = 0 # To be overwritten
data['reserved_percentage'] = self.configuration.reserved_percentage
data['QoS_support'] = False
pool = self.configuration.storwize_svc_volpool_name
backend_name = self.configuration.safe_get('volume_backend_name')
if not backend_name:
backend_name = '%s_%s' % (self._state['system_name'], pool)
data['volume_backend_name'] = backend_name
attributes = self._helpers.get_pool_attrs(pool)
if not attributes:
LOG.error(_('Could not get pool data from the storage'))
exception_message = (_('_update_volume_stats: '
'Could not get storage pool data'))
raise exception.VolumeBackendAPIException(data=exception_message)
data['total_capacity_gb'] = (float(attributes['capacity']) /
units.GiB)
data['free_capacity_gb'] = (float(attributes['free_capacity']) /
units.GiB)
data['easytier_support'] = attributes['easy_tier'] in ['on', 'auto']
data['compression_support'] = self._state['compression_enabled']
data['extent_size'] = self._state['extent_size']
data['location_info'] = ('StorwizeSVCDriver:%(sys_id)s:%(pool)s' %
{'sys_id': self._state['system_id'],
'pool': pool})
self._stats = data

View File

@ -0,0 +1,750 @@
# Copyright 2014 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.
#
import random
import re
import six
import unicodedata
from eventlet import greenthread
from cinder import context
from cinder import exception
from cinder.openstack.common import excutils
from cinder.openstack.common import log as logging
from cinder.openstack.common import loopingcall
from cinder.openstack.common import strutils
from cinder import utils
from cinder.volume.drivers.ibm.storwize_svc import ssh as storwize_ssh
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
class StorwizeHelpers(object):
def __init__(self, run_ssh):
self.ssh = storwize_ssh.StorwizeSSH(run_ssh)
self.check_fcmapping_interval = 3
@staticmethod
def handle_keyerror(cmd, out):
msg = (_('Could not find key in output of command %(cmd)s: %(out)s')
% {'out': out, 'cmd': cmd})
raise exception.VolumeBackendAPIException(data=msg)
def compression_enabled(self):
"""Return whether or not compression is enabled for this system."""
resp = self.ssh.lslicense()
keys = ['license_compression_enclosures',
'license_compression_capacity']
for key in keys:
if resp[key] != '0':
return True
return False
def get_system_info(self):
"""Return system's name, ID, and code level."""
resp = self.ssh.lssystem()
level = resp['code_level']
match_obj = re.search('([0-9].){3}[0-9]', level)
if match_obj is None:
msg = _('Failed to get code level (%s).') % str(level)
raise exception.VolumeBackendAPIException(data=msg)
code_level = match_obj.group().split('.')
return {'code_level': tuple([int(x) for x in code_level]),
'system_name': resp['name'],
'system_id': resp['id']}
def get_pool_attrs(self, pool):
"""Return attributes for the specified pool."""
return self.ssh.lsmdiskgrp(pool)
def get_available_io_groups(self):
"""Return list of available IO groups."""
iogrps = []
resp = self.ssh.lsiogrp()
for iogrp in resp:
try:
if int(iogrp['node_count']) > 0:
iogrps.append(int(iogrp['id']))
except KeyError:
self.handle_keyerror('lsiogrp', str(iogrp))
except ValueError:
msg = (_('Expected integer for node_count, '
'svcinfo lsiogrp returned: %(node)s') %
{'node': iogrp['node_count']})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return iogrps
def get_node_info(self):
"""Return dictionary containing information on system's nodes."""
nodes = {}
resp = self.ssh.lsnode()
for node_data in resp:
try:
if node_data['status'] != 'online':
continue
node = {}
node['id'] = node_data['id']
node['name'] = node_data['name']
node['IO_group'] = node_data['IO_group_id']
node['iscsi_name'] = node_data['iscsi_name']
node['WWNN'] = node_data['WWNN']
node['status'] = node_data['status']
node['WWPN'] = []
node['ipv4'] = []
node['ipv6'] = []
node['enabled_protocols'] = []
nodes[node['id']] = node
except KeyError:
self.handle_keyerror('lsnode', str(node_data))
return nodes
def add_iscsi_ip_addrs(self, storage_nodes):
"""Add iSCSI IP addresses to system node information."""
resp = self.ssh.lsportip()
for ip_data in resp:
try:
state = ip_data['state']
if ip_data['node_id'] in storage_nodes and (
state == 'configured' or state == 'online'):
node = storage_nodes[ip_data['node_id']]
if len(ip_data['IP_address']):
node['ipv4'].append(ip_data['IP_address'])
if len(ip_data['IP_address_6']):
node['ipv6'].append(ip_data['IP_address_6'])
except KeyError:
self.handle_keyerror('lsportip', str(ip_data))
def add_fc_wwpns(self, storage_nodes):
"""Add FC WWPNs to system node information."""
for key in storage_nodes:
node = storage_nodes[key]
resp = self.ssh.lsnode(node_id=node['id'])
wwpns = set(node['WWPN'])
for i, s in resp.select('port_id', 'port_status'):
if 'unconfigured' != s:
wwpns.add(i)
node['WWPN'] = list(wwpns)
LOG.info(_('WWPN on node %(node)s: %(wwpn)s')
% {'node': node['id'], 'wwpn': node['WWPN']})
def add_chap_secret_to_host(self, host_name):
"""Generate and store a randomly-generated CHAP secret for the host."""
chap_secret = utils.generate_password()
self.ssh.add_chap_secret(chap_secret, host_name)
return chap_secret
def get_chap_secret_for_host(self, host_name):
"""Generate and store a randomly-generated CHAP secret for the host."""
resp = self.ssh.lsiscsiauth()
host_found = False
for host_data in resp:
try:
if host_data['name'] == host_name:
host_found = True
if host_data['iscsi_auth_method'] == 'chap':
return host_data['iscsi_chap_secret']
except KeyError:
self.handle_keyerror('lsiscsiauth', str(host_data))
if not host_found:
msg = _('Failed to find host %s') % host_name
raise exception.VolumeBackendAPIException(data=msg)
return None
def get_conn_fc_wwpns(self, host):
wwpns = []
resp = self.ssh.lsfabric(host=host)
for wwpn in resp.select('local_wwpn'):
wwpns.append(wwpn)
return wwpns
def get_host_from_connector(self, connector):
"""Return the Storwize host described by the connector."""
LOG.debug(_('enter: get_host_from_connector: %s') % str(connector))
# If we have FC information, we have a faster lookup option
host_name = None
if 'wwpns' in connector:
for wwpn in connector['wwpns']:
resp = self.ssh.lsfabric(wwpn=wwpn)
for wwpn_info in resp:
try:
if wwpn_info['remote_wwpn'] == wwpn:
host_name = wwpn_info['name']
except KeyError:
self.handle_keyerror('lsfabric', str(wwpn_info))
# That didn't work, so try exhaustive search
if not host_name:
hosts_info = self.ssh.lshost()
for name in hosts_info.select('name'):
resp = self.ssh.lshost(host=name)
for iscsi, wwpn in resp.select('iscsi_name', 'WWPN'):
if ('initiator' in connector and
iscsi == connector['initiator']):
host_name = name
elif ('wwpns' in connector and
len(connector['wwpns']) and
wwpn.lower() in
[str(x).lower for x in connector['wwpns']]):
host_name = name
LOG.debug(_('leave: get_host_from_connector: host %s') % host_name)
return host_name
def create_host(self, connector):
"""Create a new host on the storage system.
We create a host name and associate it with the given connection
information. The host name will be a cleaned up version of the given
host name (at most 55 characters), plus a random 8-character suffix to
avoid collisions. The total length should be at most 63 characters.
"""
LOG.debug(_('enter: create_host: host %s') % connector['host'])
# Before we start, make sure host name is a string and that we have at
# least one port.
host_name = connector['host']
if not isinstance(host_name, six.string_types):
msg = _('create_host: Host name is not unicode or string')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
ports = []
if 'initiator' in connector:
ports.append(['initiator', '%s' % connector['initiator']])
if 'wwpns' in connector:
for wwpn in connector['wwpns']:
ports.append(['wwpn', '%s' % wwpn])
if not len(ports):
msg = _('create_host: No initiators or wwpns supplied.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
# Build a host name for the Storwize host - first clean up the name
if isinstance(host_name, unicode):
host_name = unicodedata.normalize('NFKD', host_name).encode(
'ascii', 'replace').decode('ascii')
for num in range(0, 128):
ch = str(chr(num))
if not ch.isalnum() and ch not in [' ', '.', '-', '_']:
host_name = host_name.replace(ch, '-')
# Storwize doesn't like hostname that doesn't starts with letter or _.
if not re.match('^[A-Za-z]', host_name):
host_name = '_' + host_name
# Add a random 8-character suffix to avoid collisions
rand_id = str(random.randint(0, 99999999)).zfill(8)
host_name = '%s-%s' % (host_name[:55], rand_id)
# Create a host with one port
port = ports.pop(0)
self.ssh.mkhost(host_name, port[0], port[1])
# Add any additional ports to the host
for port in ports:
self.ssh.addhostport(host_name, port[0], port[1])
LOG.debug(_('leave: create_host: host %(host)s - %(host_name)s') %
{'host': connector['host'], 'host_name': host_name})
return host_name
def delete_host(self, host_name):
self.ssh.rmhost(host_name)
def map_vol_to_host(self, volume_name, host_name, multihostmap):
"""Create a mapping between a volume to a host."""
LOG.debug(_('enter: map_vol_to_host: volume %(volume_name)s to '
'host %(host_name)s')
% {'volume_name': volume_name, 'host_name': host_name})
# Check if this volume is already mapped to this host
mapped = False
luns_used = []
result_lun = '-1'
resp = self.ssh.lshostvdiskmap(host_name)
for mapping_info in resp:
luns_used.append(int(mapping_info['SCSI_id']))
if mapping_info['vdisk_name'] == volume_name:
mapped = True
result_lun = mapping_info['SCSI_id']
if not mapped:
# Find unused lun
luns_used.sort()
result_lun = str(len(luns_used))
for index, n in enumerate(luns_used):
if n > index:
result_lun = str(index)
break
self.ssh.mkvdiskhostmap(host_name, volume_name, result_lun,
multihostmap)
LOG.debug(_('leave: map_vol_to_host: LUN %(result_lun)s, volume '
'%(volume_name)s, host %(host_name)s') %
{'result_lun': result_lun,
'volume_name': volume_name,
'host_name': host_name})
return result_lun
def unmap_vol_from_host(self, volume_name, host_name):
"""Unmap the volume and delete the host if it has no more mappings."""
LOG.debug(_('enter: unmap_vol_from_host: volume %(volume_name)s from '
'host %(host_name)s')
% {'volume_name': volume_name, 'host_name': host_name})
# Check if the mapping exists
resp = self.ssh.lsvdiskhostmap(volume_name)
if not len(resp):
LOG.warning(_('unmap_vol_from_host: No mapping of volume '
'%(vol_name)s to any host found.') %
{'vol_name': volume_name})
return
if host_name is None:
if len(resp) > 1:
LOG.warning(_('unmap_vol_from_host: Multiple mappings of '
'volume %(vol_name)s found, no host '
'specified.') % {'vol_name': volume_name})
return
else:
host_name = resp[0]['host_name']
else:
found = False
for h in resp.select('host_name'):
if h == host_name:
found = True
if not found:
LOG.warning(_('unmap_vol_from_host: No mapping of volume '
'%(vol_name)s to host %(host) found.') %
{'vol_name': volume_name, 'host': host_name})
# We now know that the mapping exists
self.ssh.rmvdiskhostmap(host_name, volume_name)
# If this host has no more mappings, delete it
resp = self.ssh.lshostvdiskmap(host_name)
if not len(resp):
self.delete_host(host_name)
LOG.debug(_('leave: unmap_vol_from_host: volume %(volume_name)s from '
'host %(host_name)s')
% {'volume_name': volume_name, 'host_name': host_name})
@staticmethod
def build_default_opts(config):
# Ignore capitalization
protocol = config.storwize_svc_connection_protocol
if protocol.lower() == 'fc':
protocol = 'FC'
elif protocol.lower() == 'iscsi':
protocol = 'iSCSI'
opt = {'rsize': config.storwize_svc_vol_rsize,
'warning': config.storwize_svc_vol_warning,
'autoexpand': config.storwize_svc_vol_autoexpand,
'grainsize': config.storwize_svc_vol_grainsize,
'compression': config.storwize_svc_vol_compression,
'easytier': config.storwize_svc_vol_easytier,
'protocol': protocol,
'multipath': config.storwize_svc_multipath_enabled,
'iogrp': config.storwize_svc_vol_iogrp}
return opt
@staticmethod
def check_vdisk_opts(state, opts):
# Check that rsize is either -1 or between 0 and 100
if not (opts['rsize'] >= -1 and opts['rsize'] <= 100):
raise exception.InvalidInput(
reason=_('Illegal value specified for storwize_svc_vol_rsize: '
'set to either a percentage (0-100) or -1'))
# Check that warning is either -1 or between 0 and 100
if not (opts['warning'] >= -1 and opts['warning'] <= 100):
raise exception.InvalidInput(
reason=_('Illegal value specified for '
'storwize_svc_vol_warning: '
'set to a percentage (0-100)'))
# Check that grainsize is 32/64/128/256
if opts['grainsize'] not in [32, 64, 128, 256]:
raise exception.InvalidInput(
reason=_('Illegal value specified for '
'storwize_svc_vol_grainsize: set to either '
'32, 64, 128, or 256'))
# Check that compression is supported
if opts['compression'] and not state['compression_enabled']:
raise exception.InvalidInput(
reason=_('System does not support compression'))
# Check that rsize is set if compression is set
if opts['compression'] and opts['rsize'] == -1:
raise exception.InvalidInput(
reason=_('If compression is set to True, rsize must '
'also be set (not equal to -1)'))
# Check that the requested protocol is enabled
if opts['protocol'] not in state['enabled_protocols']:
raise exception.InvalidInput(
reason=_('Illegal value %(prot)s specified for '
'storwize_svc_connection_protocol: '
'valid values are %(enabled)s')
% {'prot': opts['protocol'],
'enabled': ','.join(state['enabled_protocols'])})
if opts['iogrp'] not in state['available_iogrps']:
avail_grps = ''.join(str(e) for e in state['available_iogrps'])
raise exception.InvalidInput(
reason=_('I/O group %(iogrp)d is not valid; available '
'I/O groups are %(avail)s')
% {'iogrp': opts['iogrp'],
'avail': avail_grps})
def get_vdisk_params(self, config, state, type_id, volume_type=None):
"""Return the parameters for creating the vdisk.
Takes volume type and defaults from config options into account.
"""
opts = self.build_default_opts(config)
if volume_type is None and type_id is not None:
ctxt = context.get_admin_context()
volume_type = volume_types.get_volume_type(ctxt, type_id)
if volume_type:
specs = dict(volume_type).get('extra_specs')
for k, value in specs.iteritems():
# Get the scope, if using scope format
key_split = k.split(':')
if len(key_split) == 1:
scope = None
key = key_split[0]
else:
scope = key_split[0]
key = key_split[1]
# We generally do not look at capabilities in the driver, but
# protocol is a special case where the user asks for a given
# protocol and we want both the scheduler and the driver to act
# on the value.
if scope == 'capabilities' and key == 'storage_protocol':
scope = None
key = 'protocol'
words = value.split()
if not (words and len(words) == 2 and words[0] == '<in>'):
LOG.error(_('Protocol must be specified as '
'\'<in> iSCSI\' or \'<in> FC\'.'))
del words[0]
value = words[0]
# Any keys that the driver should look at should have the
# 'drivers' scope.
if scope and scope != 'drivers':
continue
if key in opts:
this_type = type(opts[key]).__name__
if this_type == 'int':
value = int(value)
elif this_type == 'bool':
value = strutils.bool_from_string(value)
opts[key] = value
self.check_vdisk_opts(state, opts)
return opts
@staticmethod
def _get_vdisk_create_params(opts):
easytier = 'on' if opts['easytier'] else 'off'
if opts['rsize'] == -1:
params = []
else:
params = ['-rsize', '%s%%' % str(opts['rsize']),
'-autoexpand', '-warning',
'%s%%' % str(opts['warning'])]
if not opts['autoexpand']:
params.remove('-autoexpand')
if opts['compression']:
params.append('-compressed')
else:
params.extend(['-grainsize', str(opts['grainsize'])])
params.extend(['-easytier', easytier])
return params
def create_vdisk(self, name, size, units, pool, opts):
LOG.debug(_('enter: create_vdisk: vdisk %s ') % name)
params = self._get_vdisk_create_params(opts)
self.ssh.mkvdisk(name, size, units, pool, opts, params)
LOG.debug(_('leave: _create_vdisk: volume %s ') % name)
def get_vdisk_attributes(self, vdisk):
attrs = self.ssh.lsvdisk(vdisk)
return attrs
def is_vdisk_defined(self, vdisk_name):
"""Check if vdisk is defined."""
attrs = self.get_vdisk_attributes(vdisk_name)
return attrs is not None
def _prepare_fc_map(self, fc_map_id, timeout):
self.ssh.prestartfcmap(fc_map_id)
mapping_ready = False
wait_time = 5
max_retries = (timeout / wait_time) + 1
for try_number in range(1, max_retries):
mapping_attrs = self._get_flashcopy_mapping_attributes(fc_map_id)
if (mapping_attrs is None or
'status' not in mapping_attrs):
break
if mapping_attrs['status'] == 'prepared':
mapping_ready = True
break
elif mapping_attrs['status'] == 'stopped':
self.ssh.prestartfcmap(fc_map_id)
elif mapping_attrs['status'] != 'preparing':
msg = (_('Unexecpted mapping status %(status)s for mapping'
'%(id)s. Attributes: %(attr)s')
% {'status': mapping_attrs['status'],
'id': fc_map_id,
'attr': mapping_attrs})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
greenthread.sleep(wait_time)
if not mapping_ready:
msg = (_('Mapping %(id)s prepare failed to complete within the'
'allotted %(to)d seconds timeout. Terminating.')
% {'id': fc_map_id,
'to': timeout})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def run_flashcopy(self, source, target, timeout, full_copy=True):
"""Create a FlashCopy mapping from the source to the target."""
LOG.debug(_('enter: run_flashcopy: execute FlashCopy from source '
'%(source)s to target %(target)s') %
{'source': source, 'target': target})
fc_map_id = self.ssh.mkfcmap(source, target, full_copy)
self._prepare_fc_map(fc_map_id, timeout)
self.ssh.startfcmap(fc_map_id)
LOG.debug(_('leave: run_flashcopy: FlashCopy started from '
'%(source)s to %(target)s') %
{'source': source, 'target': target})
def _get_vdisk_fc_mappings(self, vdisk):
"""Return FlashCopy mappings that this vdisk is associated with."""
mapping_ids = []
resp = self.ssh.lsvdiskfcmappings(vdisk)
for id in resp.select('id'):
mapping_ids.append(id)
return mapping_ids
def _get_flashcopy_mapping_attributes(self, fc_map_id):
resp = self.ssh.lsfcmap(fc_map_id)
if not len(resp):
return None
return resp[0]
def _check_vdisk_fc_mappings(self, name, allow_snaps=True):
"""FlashCopy mapping check helper."""
LOG.debug(_('Loopcall: _check_vdisk_fc_mappings(), vdisk %s') % name)
mapping_ids = self._get_vdisk_fc_mappings(name)
wait_for_copy = False
for map_id in mapping_ids:
attrs = self._get_flashcopy_mapping_attributes(map_id)
if not attrs:
continue
source = attrs['source_vdisk_name']
target = attrs['target_vdisk_name']
copy_rate = attrs['copy_rate']
status = attrs['status']
if copy_rate == '0':
if source == name:
# Vdisk with snapshots. Return False if snapshot
# not allowed.
if not allow_snaps:
raise loopingcall.LoopingCallDone(retvalue=False)
self.ssh.chfcmap(map_id, copyrate='50', autodel='on')
wait_for_copy = True
else:
# A snapshot
if target != name:
msg = (_('Vdisk %(name)s not involved in '
'mapping %(src)s -> %(tgt)s') %
{'name': name, 'src': source, 'tgt': target})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
if status in ['copying', 'prepared']:
self.ssh.stopfcmap(map_id)
# Need to wait for the fcmap to change to
# stopped state before remove fcmap
wait_for_copy = True
elif status in ['stopping', 'preparing']:
wait_for_copy = True
else:
self.ssh.rmfcmap(map_id)
# Case 4: Copy in progress - wait and will autodelete
else:
if status == 'prepared':
self.ssh.stopfcmap(map_id)
self.ssh.rmfcmap(map_id)
elif status == 'idle_or_copied':
# Prepare failed
self.ssh.rmfcmap(map_id)
else:
wait_for_copy = True
if not wait_for_copy or not len(mapping_ids):
raise loopingcall.LoopingCallDone(retvalue=True)
def ensure_vdisk_no_fc_mappings(self, name, allow_snaps=True):
"""Ensure vdisk has no flashcopy mappings."""
timer = loopingcall.FixedIntervalLoopingCall(
self._check_vdisk_fc_mappings, name, allow_snaps)
# Create a timer greenthread. The default volume service heart
# beat is every 10 seconds. The flashcopy usually takes hours
# before it finishes. Don't set the sleep interval shorter
# than the heartbeat. Otherwise volume service heartbeat
# will not be serviced.
LOG.debug(_('Calling _ensure_vdisk_no_fc_mappings: vdisk %s')
% name)
ret = timer.start(interval=self.check_fcmapping_interval).wait()
timer.stop()
return ret
def delete_vdisk(self, vdisk, force):
"""Ensures that vdisk is not part of FC mapping and deletes it."""
LOG.debug(_('enter: delete_vdisk: vdisk %s') % vdisk)
if not self.is_vdisk_defined(vdisk):
LOG.info(_('Tried to delete non-existant vdisk %s.') % vdisk)
return
self.ensure_vdisk_no_fc_mappings(vdisk)
self.ssh.rmvdisk(vdisk, force=force)
LOG.debug(_('leave: delete_vdisk: vdisk %s') % vdisk)
def create_copy(self, src, tgt, src_id, config, opts, full_copy):
"""Create a new snapshot using FlashCopy."""
LOG.debug(_('enter: create_copy: snapshot %(src)s to %(tgt)s') %
{'tgt': tgt, 'src': src})
src_attrs = self.get_vdisk_attributes(src)
if src_attrs is None:
msg = (_('create_copy: Source vdisk %(src)s (%(src_id)s) '
'does not exist') % {'src': src, 'src_id': src_id})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
src_size = src_attrs['capacity']
pool = config.storwize_svc_volpool_name
self.create_vdisk(tgt, src_size, 'b', pool, opts)
timeout = config.storwize_svc_flashcopy_timeout
try:
self.run_flashcopy(src, tgt, timeout, full_copy=full_copy)
except Exception:
with excutils.save_and_reraise_exception():
self.delete_vdisk(tgt, True)
LOG.debug(_('leave: _create_copy: snapshot %(tgt)s from '
'vdisk %(src)s') %
{'tgt': tgt, 'src': src})
def extend_vdisk(self, vdisk, amount):
self.ssh.expandvdisksize(vdisk, amount)
def migrate_volume_vdiskcopy(self, vdisk, dest_pool, volume_type,
state, config):
"""Migrate a volume using addvdiskcopy and rmvdiskcopy.
This will add a vdisk copy with the given volume type in the given
pool, wait until it syncs, and delete the original copy.
"""
this_pool = config.storwize_svc_volpool_name
resp = self.ssh.lsvdiskcopy(vdisk)
orig_copy_id = None
for copy_id, mdisk_grp in resp.select('copy_id', 'mdisk_grp_name'):
if mdisk_grp == this_pool:
orig_copy_id = copy_id
if orig_copy_id is None:
msg = (_('migrate_volume started without a vdisk copy in the '
'expected pool.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
if volume_type is None:
opts = self.get_vdisk_params(config, state, None)
else:
opts = self.get_vdisk_params(config, state, volume_type['id'],
volume_type=volume_type)
params = self._get_vdisk_create_params(opts)
new_copy_id = self.ssh.addvdiskcopy(vdisk, dest_pool, params)
sync = False
while not sync:
sync = self.ssh.lsvdiskcopy(vdisk, copy_id=new_copy_id)[0]['sync']
if sync == 'yes':
sync = True
else:
greenthread.sleep(10)
self.ssh.rmvdiskcopy(vdisk, orig_copy_id)
def migrate_vdisk(self, vdisk, dest_pool):
self.ssh.migratevdisk(vdisk, dest_pool)
@staticmethod
def can_migrate_to_host(host, state):
if 'location_info' not in host['capabilities']:
return None
info = host['capabilities']['location_info']
try:
(dest_type, dest_id, dest_pool) = info.split(':')
except ValueError:
return None
if (dest_type != 'StorwizeSVCDriver' or dest_id != state['system_id']):
return None
return dest_pool
def change_vdisk_options(self, vdisk, changes, opts, state):
if 'iogrp' in opts:
opts['iogrp'] = str(opts['iogrp'])
if 'warning' in opts:
opts['warning'] = '%s%%' % str(opts['warning'])
if 'easytier' in opts:
opts['easytier'] = 'on' if opts['easytier'] else 'off'
if 'autoexpand' in opts:
opts['autoexpand'] = 'on' if opts['autoexpand'] else 'off'
if 'iogrp' in changes:
changes.remove('iogrp')
if state['code_level'] < (6, 4, 0, 0):
LOG.debug(_('Ignore change IO group as storage code level '
'is %(code_level)s, below then '
'6.4.0.0') % {'code_level': state['code_level']})
else:
self.ssh.movevdisk(vdisk, opts['iogrp'])
for key in changes:
self.ssh.chvdisk(vdisk, ['-' + key, opts[key]])

View File

@ -0,0 +1,412 @@
# Copyright 2014 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.
#
import re
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common import processutils
LOG = logging.getLogger(__name__)
class StorwizeSSH(object):
"""SSH interface to IBM Storwize family and SVC storage systems."""
def __init__(self, run_ssh):
self._ssh = run_ssh
def _run_ssh(self, ssh_cmd):
try:
return self._ssh(ssh_cmd)
except processutils.ProcessExecutionError as e:
msg = (_('CLI Exception output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s') %
{'cmd': ssh_cmd,
'out': e.stdout,
'err': e.stderr})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def run_ssh_info(self, ssh_cmd, delim='!', with_header=False):
"""Run an SSH command and return parsed output."""
raw = self._run_ssh(ssh_cmd)
return CLIResponse(raw, ssh_cmd=ssh_cmd, delim=delim,
with_header=with_header)
def run_ssh_assert_no_output(self, ssh_cmd):
"""Run an SSH command and assert no output returned."""
out, err = self._run_ssh(ssh_cmd)
if len(out.strip()) != 0:
msg = (_('Expected no output from CLI command %(cmd)s, '
'got %(out)s') % {'cmd': ' '.join(ssh_cmd), 'out': out})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def run_ssh_check_created(self, ssh_cmd):
"""Run an SSH command and return the ID of the created object."""
out, err = self._run_ssh(ssh_cmd)
try:
match_obj = re.search(r'\[([0-9]+)\],? successfully created', out)
return match_obj.group(1)
except (AttributeError, IndexError):
msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s') %
{'cmd': ssh_cmd,
'out': out,
'err': err})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def lsnode(self, node_id=None):
with_header = True
ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!']
if node_id:
with_header = False
ssh_cmd.append(node_id)
return self.run_ssh_info(ssh_cmd, with_header=with_header)
def lslicense(self):
ssh_cmd = ['svcinfo', 'lslicense', '-delim', '!']
return self.run_ssh_info(ssh_cmd)[0]
def lssystem(self):
ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
return self.run_ssh_info(ssh_cmd)[0]
def lsmdiskgrp(self, pool):
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
return self.run_ssh_info(ssh_cmd)[0]
def lsiogrp(self):
ssh_cmd = ['svcinfo', 'lsiogrp', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsportip(self):
ssh_cmd = ['svcinfo', 'lsportip', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
@staticmethod
def _create_port_arg(port_type, port_name):
if port_type == 'initiator':
port = ['-iscsiname']
else:
port = ['-hbawwpn']
port.append(port_name)
return port
def mkhost(self, host_name, port_type, port_name):
port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'mkhost', '-force'] + port + ['-name', host_name]
return self.run_ssh_check_created(ssh_cmd)
def addhostport(self, host, port_type, port_name):
port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'addhostport', '-force'] + port + [host]
self.run_ssh_assert_no_output(ssh_cmd)
def lshost(self, host=None):
with_header = True
ssh_cmd = ['svcinfo', 'lshost', '-delim', '!']
if host:
with_header = False
ssh_cmd.append(host)
return self.run_ssh_info(ssh_cmd, with_header=with_header)
def add_chap_secret(self, secret, host):
ssh_cmd = ['svctask', 'chhost', '-chapsecret', secret, host]
self.run_ssh_assert_no_output(ssh_cmd)
def lsiscsiauth(self):
ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsfabric(self, wwpn=None, host=None):
if wwpn:
ssh_cmd = ['svcinfo', 'lsfabric', '-wwpn', wwpn, '-delim', '!']
elif host:
ssh_cmd = ['svcinfo', 'lsfabric', '-host', host]
else:
msg = (_('Must pass wwpn or host to lsfabric.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
return self.run_ssh_info(ssh_cmd, with_header=True)
def mkvdiskhostmap(self, host, vdisk, lun, multihostmap):
"""Map vdisk to host.
If vdisk already mapped and multihostmap is True, use the force flag.
"""
ssh_cmd = ['svctask', 'mkvdiskhostmap', '-host', host,
'-scsi', lun, vdisk]
out, err = self._ssh(ssh_cmd, check_exit_code=False)
if 'successfully created' in out:
return
if not err:
msg = (_('Did not find success message nor error for %(fun)s: '
'%(out)s') % {'out': out, 'fun': ssh_cmd})
raise exception.VolumeBackendAPIException(data=msg)
if err.startswith('CMMVC6071E'):
if not multihostmap:
LOG.error(_('storwize_svc_multihostmap_enabled is set '
'to False, not allowing multi host mapping.'))
msg = 'CMMVC6071E The VDisk-to-host mapping '\
'was not created because the VDisk is '\
'already mapped to a host.\n"'
raise exception.VolumeDriverException(message=msg)
ssh_cmd.insert(ssh_cmd.index('mkvdiskhostmap') + 1, '-force')
return self.run_ssh_check_created(ssh_cmd)
def rmvdiskhostmap(self, host, vdisk):
ssh_cmd = ['svctask', 'rmvdiskhostmap', '-host', host, vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def lsvdiskhostmap(self, vdisk):
ssh_cmd = ['svcinfo', 'lsvdiskhostmap', '-delim', '!', vdisk]
return self.run_ssh_info(ssh_cmd, with_header=True)
def lshostvdiskmap(self, host):
ssh_cmd = ['svcinfo', 'lshostvdiskmap', '-delim', '!', host]
return self.run_ssh_info(ssh_cmd, with_header=True)
def rmhost(self, host):
ssh_cmd = ['svctask', 'rmhost', host]
self.run_ssh_assert_no_output(ssh_cmd)
def mkvdisk(self, name, size, units, pool, opts, params):
ssh_cmd = ['svctask', 'mkvdisk', '-name', name, '-mdiskgrp', pool,
'-iogrp', str(opts['iogrp']), '-size', size, '-unit',
units] + params
return self.run_ssh_check_created(ssh_cmd)
def rmvdisk(self, vdisk, force=True):
ssh_cmd = ['svctask', 'rmvdisk']
if force:
ssh_cmd += ['-force']
ssh_cmd += [vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def lsvdisk(self, vdisk):
"""Return vdisk attributes or None if it doesn't exist."""
ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!', vdisk]
out, err = self._ssh(ssh_cmd, check_exit_code=False)
if not len(err):
return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
with_header=False)[0]
if err.startswith('CMMVC5754E'):
return None
msg = (_('CLI Exception output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s') %
{'cmd': ssh_cmd,
'out': out,
'err': err})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def chvdisk(self, vdisk, params):
ssh_cmd = ['svctask', 'chvdisk'] + params + [vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def movevdisk(self, vdisk, iogrp):
ssh_cmd = ['svctask', 'movevdisk', '-iogrp', iogrp, vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def expandvdisksize(self, vdisk, amount):
ssh_cmd = (['svctask', 'expandvdisksize', '-size', str(amount),
'-unit', 'gb', vdisk])
self.run_ssh_assert_no_output(ssh_cmd)
def migratevdisk(self, vdisk, dest_pool):
ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool,
'-vdisk', vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def mkfcmap(self, source, target, full_copy):
ssh_cmd = ['svctask', 'mkfcmap', '-source', source, '-target',
target, '-autodelete']
if not full_copy:
ssh_cmd.extend(['-copyrate', '0'])
out, err = self._ssh(ssh_cmd, check_exit_code=False)
if 'successfully created' not in out:
msg = (_('CLI Exception output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s') %
{'cmd': ssh_cmd,
'out': out,
'err': err})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
match_obj = re.search(r'FlashCopy Mapping, id \[([0-9]+)\], '
'successfully created', out)
fc_map_id = match_obj.group(1)
except (AttributeError, IndexError):
msg = (_('Failed to parse CLI output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s') %
{'cmd': ssh_cmd,
'out': out,
'err': err})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return fc_map_id
def prestartfcmap(self, fc_map_id):
ssh_cmd = ['svctask', 'prestartfcmap', fc_map_id]
self.run_ssh_assert_no_output(ssh_cmd)
def startfcmap(self, fc_map_id):
ssh_cmd = ['svctask', 'startfcmap', fc_map_id]
self.run_ssh_assert_no_output(ssh_cmd)
def chfcmap(self, fc_map_id, copyrate='50', autodel='on'):
ssh_cmd = ['svctask', 'chfcmap', '-copyrate', copyrate,
'-autodelete', autodel, fc_map_id]
self.run_ssh_assert_no_output(ssh_cmd)
def stopfcmap(self, fc_map_id):
ssh_cmd = ['svctask', 'stopfcmap', fc_map_id]
self.run_ssh_assert_no_output(ssh_cmd)
def rmfcmap(self, fc_map_id):
ssh_cmd = ['svctask', 'rmfcmap', '-force', fc_map_id]
self.run_ssh_assert_no_output(ssh_cmd)
def lsvdiskfcmappings(self, vdisk):
ssh_cmd = ['svcinfo', 'lsvdiskfcmappings', '-delim', '!', vdisk]
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsfcmap(self, fc_map_id):
ssh_cmd = ['svcinfo', 'lsfcmap', '-filtervalue',
'id=%s' % fc_map_id, '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
def addvdiskcopy(self, vdisk, dest_pool, params):
ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp',
dest_pool, vdisk])
return self.run_ssh_check_created(ssh_cmd)
def lsvdiskcopy(self, vdisk, copy_id=None):
ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!']
with_header = True
if copy_id:
ssh_cmd += ['-copy', copy_id]
with_header = False
ssh_cmd += [vdisk]
return self.run_ssh_info(ssh_cmd, with_header=with_header)
def rmvdiskcopy(self, vdisk, copy_id):
ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', copy_id, vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
class CLIResponse(object):
'''Parse SVC CLI output and generate iterable.'''
def __init__(self, raw, ssh_cmd=None, delim='!', with_header=True):
super(CLIResponse, self).__init__()
if ssh_cmd:
self.ssh_cmd = ' '.join(ssh_cmd)
else:
self.ssh_cmd = 'None'
self.raw = raw
self.delim = delim
self.with_header = with_header
self.result = self._parse()
def select(self, *keys):
for a in self.result:
vs = []
for k in keys:
v = a.get(k, None)
if isinstance(v, basestring) or v is None:
v = [v]
if isinstance(v, list):
vs.append(v)
for item in zip(*vs):
if len(item) == 1:
yield item[0]
else:
yield item
def __getitem__(self, key):
try:
return self.result[key]
except KeyError:
msg = (_('Did not find expected key %(key)s in %(fun)s: %(raw)s') %
{'key': key, 'fun': self.ssh_cmd, 'raw': self.raw})
raise exception.VolumeBackendAPIException(data=msg)
def __iter__(self):
for a in self.result:
yield a
def __len__(self):
return len(self.result)
def _parse(self):
def get_reader(content, delim):
for line in content.lstrip().splitlines():
line = line.strip()
if line:
yield line.split(delim)
else:
yield []
if isinstance(self.raw, basestring):
stdout, stderr = self.raw, ''
else:
stdout, stderr = self.raw
reader = get_reader(stdout, self.delim)
result = []
if self.with_header:
hds = tuple()
for row in reader:
hds = row
break
for row in reader:
cur = dict()
if len(hds) != len(row):
msg = (_('Unexpected CLI response: header/row mismatch. '
'header: %(header)s, row: %(row)s')
% {'header': str(hds), 'row': str(row)})
raise exception.VolumeBackendAPIException(data=msg)
for k, v in zip(hds, row):
CLIResponse.append_dict(cur, k, v)
result.append(cur)
else:
cur = dict()
for row in reader:
if row:
CLIResponse.append_dict(cur, row[0], ' '.join(row[1:]))
elif cur: # start new section
result.append(cur)
cur = dict()
if cur:
result.append(cur)
return result
@staticmethod
def append_dict(dict_, key, value):
key, value = key.strip(), value.strip()
obj = dict_.get(key, None)
if obj is None:
dict_[key] = value
elif isinstance(obj, list):
obj.append(value)
dict_[key] = obj
else:
dict_[key] = [obj, value]
return dict_

File diff suppressed because it is too large Load Diff

View File

@ -103,7 +103,9 @@ MAPPING = {
'cinder.volume.drivers.solidfire.SolidFire':
'cinder.volume.drivers.solidfire.SolidFireDriver',
'cinder.volume.storwize_svc.StorwizeSVCDriver':
'cinder.volume.drivers.storwize_svc.StorwizeSVCDriver',
'cinder.volume.drivers.ibm.storwize_svc.StorwizeSVCDriver',
'cinder.volume.drivers.storwize_svc.StorwizeSVCDriver':
'cinder.volume.drivers.ibm.storwize_svc.StorwizeSVCDriver',
'cinder.volume.windows.WindowsDriver':
'cinder.volume.drivers.windows.windows.WindowsDriver',
'cinder.volume.drivers.windows.WindowsDriver':

View File

@ -1155,6 +1155,59 @@
#cinder_huawei_conf_file=/etc/cinder/cinder_huawei_conf.xml
#
# Options defined in cinder.volume.drivers.ibm.storwize_svc
#
# Storage system storage pool for volumes (string value)
#storwize_svc_volpool_name=volpool
# Storage system space-efficiency parameter for volumes
# (percentage) (integer value)
#storwize_svc_vol_rsize=2
# Storage system threshold for volume capacity warnings
# (percentage) (integer value)
#storwize_svc_vol_warning=0
# Storage system autoexpand parameter for volumes (True/False)
# (boolean value)
#storwize_svc_vol_autoexpand=true
# Storage system grain size parameter for volumes
# (32/64/128/256) (integer value)
#storwize_svc_vol_grainsize=256
# Storage system compression option for volumes (boolean
# value)
#storwize_svc_vol_compression=false
# Enable Easy Tier for volumes (boolean value)
#storwize_svc_vol_easytier=true
# The I/O group in which to allocate volumes (integer value)
#storwize_svc_vol_iogrp=0
# Maximum number of seconds to wait for FlashCopy to be
# prepared. Maximum value is 600 seconds (10 minutes) (integer
# value)
#storwize_svc_flashcopy_timeout=120
# Connection protocol (iSCSI/FC) (string value)
#storwize_svc_connection_protocol=iSCSI
# Configure CHAP authentication for iSCSI connections
# (Default: Enabled) (boolean value)
#storwize_svc_iscsi_chap_enabled=true
# Connect with multipath (FC only; iSCSI multipath is
# controlled by Nova) (boolean value)
#storwize_svc_multipath_enabled=false
# Allows vdisk to multi host mapping (boolean value)
#storwize_svc_multihostmap_enabled=true
#
# Options defined in cinder.volume.drivers.lvm
#
@ -1523,59 +1576,6 @@
#sf_api_port=443
#
# Options defined in cinder.volume.drivers.storwize_svc
#
# Storage system storage pool for volumes (string value)
#storwize_svc_volpool_name=volpool
# Storage system space-efficiency parameter for volumes
# (percentage) (integer value)
#storwize_svc_vol_rsize=2
# Storage system threshold for volume capacity warnings
# (percentage) (integer value)
#storwize_svc_vol_warning=0
# Storage system autoexpand parameter for volumes (True/False)
# (boolean value)
#storwize_svc_vol_autoexpand=true
# Storage system grain size parameter for volumes
# (32/64/128/256) (integer value)
#storwize_svc_vol_grainsize=256
# Storage system compression option for volumes (boolean
# value)
#storwize_svc_vol_compression=false
# Enable Easy Tier for volumes (boolean value)
#storwize_svc_vol_easytier=true
# The I/O group in which to allocate volumes (integer value)
#storwize_svc_vol_iogrp=0
# Maximum number of seconds to wait for FlashCopy to be
# prepared. Maximum value is 600 seconds (10 minutes) (integer
# value)
#storwize_svc_flashcopy_timeout=120
# Connection protocol (iSCSI/FC) (string value)
#storwize_svc_connection_protocol=iSCSI
# Configure CHAP authentication for iSCSI connections
# (Default: Enabled) (boolean value)
#storwize_svc_iscsi_chap_enabled=true
# Connect with multipath (FC only; iSCSI multipath is
# controlled by Nova) (boolean value)
#storwize_svc_multipath_enabled=false
# Allows vdisk to multi host mapping (boolean value)
#storwize_svc_multihostmap_enabled=true
#
# Options defined in cinder.volume.drivers.vmware.vmdk
#