Merge "Storwize driver cleanup"
This commit is contained in:
commit
3923e5000c
|
@ -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')
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -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
|
|
@ -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]])
|
|
@ -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
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue