From: Li Min Liu Date: Thu, 9 Jan 2014 06:54:09 +0000 (+0800) Subject: Add support for retype in Storwize/SVC driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=abb444f2d349b8f8c405a8636046fe4e910928aa;p=openstack-build%2Fcinder-build.git Add support for retype in Storwize/SVC driver In Storwize/SVC, retype can be handled by a few cases: 1. The change may just require setting an option on the volume 2. The change may require moving the data in the same pool (e.g., compression) 3. The scheduler may have chosen a different pool virtualized by the same Storwize or SVC, in which case we move the data as in #2 Implements: blueprint storwize-svc-retype Change-Id: Ic2ed17bce56b5ea65e1596acfa85720cf4818fa8 --- diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py index 07d6b5768..15c23fabc 100644 --- a/cinder/tests/test_storwize_svc.py +++ b/cinder/tests/test_storwize_svc.py @@ -14,10 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. # -# Authors: -# Ronen Kat -# Avishay Traeger - """ Tests for the IBM Storwize family and SVC volume driver. """ @@ -129,6 +125,8 @@ class StorwizeSVCManagementSimulator: 'CMMVC5903E': ('', 'CMMVC5903E The FlashCopy mapping was not ' 'changed because the mapping or consistency ' 'group is another state.'), + 'CMMVC5709E': ('', 'CMMVC5709E [-%(VALUE)s] is not a supported ' + 'parameter.'), } self._transitions = {'begin': {'make': 'idle_or_copied'}, 'idle_or_copied': {'prepare': 'preparing', @@ -190,7 +188,6 @@ class StorwizeSVCManagementSimulator: def _cmd_to_dict(self, arg_list): no_param_args = [ 'autodelete', - 'autoexpand', 'bytes', 'compressed', 'force', @@ -221,6 +218,9 @@ class StorwizeSVCManagementSimulator: 'warning', 'wwpn', ] + no_or_one_param_args = [ + 'autoexpand', + ] # Handle the special case of lsnode which is a two-word command # Use the one word version of the command internally @@ -247,6 +247,12 @@ class StorwizeSVCManagementSimulator: elif arg_list[i][1:] in one_param_args: ret[arg_list[i][1:]] = arg_list[i + 1] skip = True + elif arg_list[i][1:] in no_or_one_param_args: + if i == (len(arg_list) - 1) or arg_list[i + 1][0] == '-': + ret[arg_list[i][1:]] = True + else: + ret[arg_list[i][1:]] = arg_list[i + 1] + skip = True else: raise exception.InvalidInput( reason=_('unrecognized argument %s') % arg_list[i]) @@ -310,9 +316,10 @@ class StorwizeSVCManagementSimulator: # Print mostly made-up stuff in the correct syntax def _cmd_lssystem(self, **kwargs): - rows = [None] * 2 + rows = [None] * 3 rows[0] = ['id', '0123456789ABCDEF'] rows[1] = ['name', 'storwize-svc-sim'] + rows[2] = ['code_level', '7.2.0.0 (build 87.0.1311291000)'] return self._print_info_cmd(rows=rows, **kwargs) # Print mostly made-up stuff in the correct syntax, assume -bytes passed @@ -688,9 +695,11 @@ port_speed!N/A rows.append(['IO_group_name', vol['IO_group_name']]) rows.append(['status', 'online']) rows.append(['mdisk_grp_id', '0']) - rows.append([ - 'mdisk_grp_name', - self._flags['storwize_svc_volpool_name']]) + 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']) @@ -1322,6 +1331,51 @@ port_speed!N/A if copy_id not in vol['copies']: 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): + if 'obj' not in kwargs: + return self._errors['CMMVC5701E'] + vol_name = kwargs['obj'].strip('\'\'') + vol = self._volumes_list[vol_name] + kwargs.pop('obj') + + params = ['name', 'warning', 'udid', 'autoexpand', 'easytier'] + for key, value in kwargs.iteritems(): + if key == 'easytier': + vol['easy_tier'] = value + continue + if key == 'warning': + vol['warning'] = value.rstrip('%') + continue + if key in params: + vol[key] = value + else: + err = self._errors['CMMVC5709E'][1] % {'VALUE': key} + return ('', err) + return ('', '') + + def _cmd_movevdisk(self, **kwargs): + if 'obj' not in kwargs: + return self._errors['CMMVC5701E'] + vol_name = kwargs['obj'].strip('\'\'') + vol = self._volumes_list[vol_name] + + if 'iogrp' not in kwargs: + return self._errors['CMMVC5707E'] + + iogrp = kwargs['iogrp'] + if iogrp.isdigit(): + vol['IO_group_id'] = iogrp + vol['IO_group_name'] = 'io_grp%s' % iogrp + else: + vol['IO_group_id'] = iogrp[6:] + vol['IO_group_name'] = iogrp return ('', '') def _add_host_to_list(self, connector): @@ -1420,6 +1474,10 @@ port_speed!N/A out, err = self._cmd_lsvdiskcopy(**kwargs) elif command == 'rmvdiskcopy': out, err = self._cmd_rmvdiskcopy(**kwargs) + elif command == 'chvdisk': + out, err = self._cmd_chvdisk(**kwargs) + elif command == 'movevdisk': + out, err = self._cmd_movevdisk(**kwargs) else: out, err = ('', 'ERROR: Unsupported command') @@ -1619,18 +1677,23 @@ class StorwizeSVCDriverTestCase(test.TestCase): 'volume_name': vol_name, 'id': rand_id, 'volume_id': vol_id, - 'volume_size': 10} + 'volume_size': 10, + 'mdisk_grp_name': 'openstack'} else: return {'name': 'test_volume%s' % rand_id, 'size': 10, 'id': '%s' % rand_id, - 'volume_type_id': None} + 'volume_type_id': None, + 'mdisk_grp_name': 'openstack'} def _create_test_vol(self, opts): ctxt = context.get_admin_context() type_ref = volume_types.create(ctxt, 'testtype', opts) volume = self._generate_vol_info(None, None) - volume['volume_type_id'] = type_ref['id'] + type_id = type_ref['id'] + type_ref = volume_types.get_volume_type(ctxt, type_id) + volume['volume_type_id'] = type_id + volume['volume_type'] = type_ref self.driver.create_volume(volume) attrs = self.driver._get_vdisk_attributes(volume['name']) @@ -2264,8 +2327,104 @@ class StorwizeSVCDriverTestCase(test.TestCase): self.driver.create_volume(volume) self.assertNotEqual(cap['extent_size'], self.driver._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') 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' + cap = {'location_info': loc, 'extent_size': '128'} + self.driver._stats = {'location_info': loc} + host = {'host': 'foo', 'capabilities': cap} + ctxt = context.get_admin_context() + + key_specs_old = {'easytier': False, 'warning': 2, 'autoexpand': True} + key_specs_new = {'easytier': True, 'warning': 5, 'autoexpand': False} + 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'], + new_type_ref['id']) + + volume = self._generate_vol_info(None, None) + old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) + volume['volume_type'] = old_type + volume['host'] = host + new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) + + self.driver.create_volume(volume) + self.driver.retype(ctxt, volume, new_type, diff, host) + attrs = self.driver._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') + self.driver.delete_volume(volume) + + def test_storwize_svc_retype_only_change_iogrp(self): + self.driver.do_setup(None) + loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack' + cap = {'location_info': loc, 'extent_size': '128'} + self.driver._stats = {'location_info': loc} + host = {'host': 'foo', 'capabilities': cap} + ctxt = context.get_admin_context() + + key_specs_old = {'iogrp': 0} + key_specs_new = {'iogrp': 1} + old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) + new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) + + diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], + new_type_ref['id']) + + volume = self._generate_vol_info(None, None) + old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) + volume['volume_type'] = old_type + volume['host'] = host + new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) + + self.driver.create_volume(volume) + self.driver.retype(ctxt, volume, new_type, diff, host) + attrs = self.driver._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' + cap = {'location_info': loc, 'extent_size': '128'} + self.driver._stats = {'location_info': loc} + host = {'host': 'foo', 'capabilities': cap} + ctxt = context.get_admin_context() + + key_specs_old = {'compression': True} + key_specs_new = {'compression': False} + old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) + new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) + + diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], + new_type_ref['id']) + + volume = self._generate_vol_info(None, None) + old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) + volume['volume_type'] = old_type + volume['host'] = host + new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) + + 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') + 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') + class CLIResponseTestCase(test.TestCase): def test_empty(self): diff --git a/cinder/volume/drivers/storwize_svc.py b/cinder/volume/drivers/storwize_svc.py index 244a47329..9a13d6b59 100644 --- a/cinder/volume/drivers/storwize_svc.py +++ b/cinder/volume/drivers/storwize_svc.py @@ -14,10 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. # -# Authors: -# Ronen Kat -# Avishay Traeger - """ Volume driver for IBM Storwize family and SVC storage systems. @@ -122,13 +118,13 @@ class StorwizeSVCDriver(san.SanDriver): 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 """ """=====================================================================""" """ SETUP """ """=====================================================================""" - VERSION = "1.1.0" + VERSION = "1.2.0" def __init__(self, *args, **kwargs): super(StorwizeSVCDriver, self).__init__(*args, **kwargs) @@ -141,6 +137,7 @@ class StorwizeSVCDriver(san.SanDriver): self._system_name = None self._system_id = None self._extent_size = None + self._code_level = None # Build cleanup translation tables for host names invalid_ch_in_host = '' @@ -192,6 +189,12 @@ class StorwizeSVCDriver(san.SanDriver): LOG.info(_('WWPN on node %(node)s: %(wwpn)s') % {'node': node['id'], 'wwpn': node['WWPN']}) + def _get_code_level(self, level): + match_obj = re.search('([0-9].){3}[0-9]', level) + self._driver_assert(match_obj is not None, _('Get code level failed')) + code_level = match_obj.group().split('.') + return tuple(map(int, code_level)) + def do_setup(self, ctxt): """Check that we have all configuration details from the storage.""" @@ -207,15 +210,11 @@ class StorwizeSVCDriver(san.SanDriver): raise exception.VolumeBackendAPIException(data=msg) self._system_name = attributes['name'] self._system_id = attributes['id'] + self._code_level = self._get_code_level(attributes['code_level']) # Validate that the pool exists pool = self.configuration.storwize_svc_volpool_name - ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool] - attributes = self._execute_command_and_parse_attributes(ssh_cmd) - if not attributes: - msg = (_('do_setup: Pool %s does not exist') % pool) - LOG.error(msg) - raise exception.InvalidInput(reason=msg) + attributes = self._get_pool_attrs(pool) self._extent_size = attributes['extent_size'] # Check if compression is supported @@ -533,11 +532,10 @@ class StorwizeSVCDriver(san.SanDriver): 'initiator' in connector and attr_val == connector['initiator']): return host - elif (attr_name == 'WWPN' and - 'wwpns' in connector and + elif (attr_name == 'WWPN' and 'wwpns' in connector and attr_val.lower() in map(str.lower, map(str, connector['wwpns']))): - return host + return host return None def _get_host_from_connector(self, connector): @@ -808,11 +806,11 @@ class StorwizeSVCDriver(san.SanDriver): 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)) - exception_msg = (_('initialize_connection: Missing volume ' - 'attribute for volume %s') % volume_name) - raise exception.VolumeBackendAPIException(data=exception_msg) + LOG.error(_('Did not find expected column name in ' + 'lsvdisk: %s') % str(e)) + exception_msg = (_('initialize_connection: Missing volume ' + 'attribute for volume %s') % volume_name) + raise exception.VolumeBackendAPIException(data=exception_msg) try: # Get preferred node and other nodes in I/O group @@ -978,12 +976,13 @@ class StorwizeSVCDriver(san.SanDriver): mapping_ids = [line.split()[0] for line in lines] return mapping_ids - def _get_vdisk_params(self, type_id): + def _get_vdisk_params(self, type_id, volume_type=None): opts = self._build_default_opts() - if type_id: + 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) - specs = volume_type.get('extra_specs') + 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(':') @@ -1010,7 +1009,7 @@ class StorwizeSVCDriver(san.SanDriver): del words[0] value = words[0] - # Anything keys that the driver should look at should have the + # Any keys that the driver should look at should have the # 'drivers' scope. if scope and scope != "drivers": continue @@ -1456,6 +1455,108 @@ class StorwizeSVCDriver(san.SanDriver): ssh_cmd, out, err) LOG.debug(_('leave: extend_volume: volume %s') % volume['id']) + def _add_vdisk_copy(self, volume, dest_pool, opts): + """Create a vdisk copy for the given volume.""" + params = self._get_vdisk_create_params(opts) + ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp', + dest_pool, volume['name']]) + out, err = self._run_ssh(ssh_cmd) + self._assert_ssh_return(len(out.strip()), '_add_vdisk_copy', + ssh_cmd, out, err) + + # Ensure that the output is as expected + match_obj = re.search('Vdisk \[([0-9]+)\] copy \[([0-9]+)\] ' + 'successfully created', out) + # Make sure we got a "successfully created" message with copy id + self._driver_assert( + match_obj is not None, + _('_add_vdisk_copy %(name)s - did not find ' + 'success message in CLI output.\n ' + 'stdout: %(out)s\n stderr: %(err)s') + % {'name': volume['name'], 'out': str(out), 'err': str(err)}) + + copy_id = match_obj.group(2) + return copy_id + + def _get_vdisk_copy_attrs(self, volume, copy_id): + ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!', '-copy', + copy_id, volume['name']] + attrs = self._execute_command_and_parse_attributes(ssh_cmd) + if not attrs: + msg = (_('_get_vdisk_copy_attrs: Could not get vdisk ' + 'copy data')) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + return attrs + + def _wait_for_vdisk_copy_sync(self, volume, copy_id): + sync = False + while not sync: + attrs = self._get_vdisk_copy_attrs(volume, copy_id) + if attrs['sync'] == 'yes': + sync = True + else: + time.sleep(5) + + def _remove_vdisk_copy(self, volume, copy_id): + ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', copy_id, + volume['name']] + out, err = self._run_ssh(ssh_cmd) + # No output should be returned from rmvdiskcopy + self._assert_ssh_return(len(out.strip()) == 0, '_remove_vdisk_copy', + ssh_cmd, out, err) + + def _migrate_volume_vdiskcopy(self, volume, dest_pool, volume_type): + """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 = self.configuration.storwize_svc_volpool_name + orig_copy_id = self._find_vdisk_copy_id(this_pool, volume['name']) + self._driver_assert(orig_copy_id is not None, + _('migrate_volume started without a vdisk ' + 'copy in the expected pool.')) + + if volume_type is None: + opts = self._get_vdisk_params(None) + else: + opts = self._get_vdisk_params(volume_type['id'], + volume_type=volume_type) + new_copy_id = self._add_vdisk_copy(volume, dest_pool, opts) + self._wait_for_vdisk_copy_sync(volume, new_copy_id) + self._remove_vdisk_copy(volume, orig_copy_id) + + def _same_host(self, host): + dest_location = host['capabilities'].get('location_info') + if self._stats['location_info'] == dest_location: + return True + return False + + def _can_migrate_to_host(self, host): + 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 != self._system_id): + return None + return dest_pool + + @staticmethod + def _convert_vdisk_parameters(opts): + 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' + return opts + def migrate_volume(self, ctxt, volume, host): """Migrate directly if source and dest are managed by same storage. @@ -1474,14 +1575,8 @@ class StorwizeSVCDriver(san.SanDriver): {'id': volume['id'], 'host': host['host']}) false_ret = (False, None) - if 'location_info' not in host['capabilities']: - return false_ret - info = host['capabilities']['location_info'] - try: - (dest_type, dest_id, dest_pool) = info.split(':') - except ValueError: - return false_ret - if (dest_type != 'StorwizeSVCDriver' or dest_id != self._system_id): + dest_pool = self._can_migrate_to_host(host) + if dest_pool is None: return false_ret if 'extent_size' not in host['capabilities']: @@ -1496,58 +1591,98 @@ class StorwizeSVCDriver(san.SanDriver): ssh_cmd, out, err) else: # If source and dest pool extent size differ, add/delete vdisk copy - copy_info = self._get_vdisk_copy_info(volume['name']) - copies = list(copy_info.keys()) - self._driver_assert(len(copies) == 1, - _('migrate_volume started with more than one ' - 'vdisk copy')) - orig_copy_id = copies[0] - - opts = self._get_vdisk_params(volume['volume_type_id']) - params = self._get_vdisk_create_params(opts) - ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp', - dest_pool, volume['name']]) - out, err = self._run_ssh(ssh_cmd) - self._assert_ssh_return(len(out.strip()), 'migrate_volume', - ssh_cmd, out, err) - - # Ensure that the output is as expected - match_obj = re.search('Vdisk \[([0-9]+)\] copy \[([0-9]+)\] ' - 'successfully created', out) - # Make sure we got a "successfully created" message with copy id - self._driver_assert( - match_obj is not None, - _('migrate_volume %(name)s - did not find ' - 'success message in CLI output.\n ' - 'stdout: %(out)s\n stderr: %(err)s') - % {'name': volume['name'], 'out': str(out), 'err': str(err)}) - - copy_id = match_obj.group(2) - sync = False - while not sync: - ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!', '-copy', - copy_id, volume['name']] - attrs = self._execute_command_and_parse_attributes(ssh_cmd) - if not attrs: - msg = (_('migrate_volume: Could not get vdisk copy data')) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - if attrs['sync'] == 'yes': - sync = True - else: - time.sleep(5) - ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', orig_copy_id, - volume['name']] - out, err = self._run_ssh(ssh_cmd) - # No output should be returned from rmvdiskcopy - self._assert_ssh_return(len(out.strip()) == 0, 'migrate_volume', - ssh_cmd, out, err) + 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._migrate_volume_vdiskcopy(volume, dest_pool, vol_type) 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) + + if not self._same_host(host): + need_copy = True + + if need_copy: + dest_pool = self._can_migrate_to_host(host) + if dest_pool is None: + return False + + self._migrate_volume_vdiskcopy(volume, dest_pool, new_type) + else: + new_opts = self._convert_vdisk_parameters(new_opts) + if 'iogrp' in vdisk_changes: + vdisk_changes.remove('iogrp') + if self._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': self._code_level}) + else: + ssh_cmd = (['svctask', 'movevdisk', '-iogrp', + new_opts['iogrp']] + [volume['name']]) + out, err = self._run_ssh(ssh_cmd) + self._assert_ssh_return(len(out.strip()) == 0, + 'movevdisk', ssh_cmd, out, err) + params = [] + for key in vdisk_changes: + params.extend(['-' + key, new_opts[key]]) + if params: + ssh_cmd = (['svctask', 'chvdisk'] + params + [volume['name']]) + out, err = self._run_ssh(ssh_cmd) + self._assert_ssh_return(len(out.strip()) == 0, + 'retype', ssh_cmd, out, err) + + 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 report_volume_migrate_status(self): + pass + """=====================================================================""" """ MISC/HELPERS """ """=====================================================================""" @@ -1605,6 +1740,22 @@ class StorwizeSVCDriver(san.SanDriver): self._stats = data + def _get_pool_attrs(self, pool): + ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool] + attributes = self._execute_command_and_parse_attributes(ssh_cmd) + if not attributes: + msg = (_('_get_pool_attrs: Pool %s does not exist') % pool) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + return attributes + + def _find_vdisk_copy_id(self, pool, name): + copies_info = self._get_vdisk_copy_info(name) + for cid, cinfo in copies_info.iteritems(): + if cinfo['mdisk_grp_name'] == pool: + return cid + return None + def _port_conf_generator(self, cmd): ssh_cmd = cmd + ['-delim', '!'] out, err = self._run_ssh(ssh_cmd) @@ -1648,7 +1799,8 @@ class StorwizeSVCDriver(san.SanDriver): ret[copy_data['copy_id']] = copy_data return ret - def _get_vdisk_create_params(self, opts): + @staticmethod + def _get_vdisk_create_params(opts): easytier = 'on' if opts['easytier'] else 'off' # Set space-efficient options