Storwize: add hyperswap volume support

This patch adds hyperswap volume support for Storwize/SVC driver.

New properties are added in volume type configuration:
drivers:volume_topology sets to "hyperswap" would specify a
hyperswap volume.
drivers:peer_pool specifies the pool that hyperswap remote copy
volume is stored.
drivers:host_site specifies the site name of the host.

New property is added in group type configuration:
hyperswap_group_enabled="<is> True" would setup a hyperswap group.

DocImpact
Implements: blueprint svc-hyperswap-support

Change-Id: I989d22d9580c1f44546f371af1fffded14ddcbe3
This commit is contained in:
yixuanzhang 2017-08-10 15:11:37 +08:00
parent ada2ab7dac
commit c0d471a424
7 changed files with 1961 additions and 116 deletions

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,7 @@ REPLICA_AUX_VOL_PREFIX = 'aux_'
REPLICA_CHG_VOL_PREFIX = 'chg_'
RCCG_PREFIX = 'rccg-'
HYPERCG_PREFIX = 'hycg-'
# remote mirror copy status
REP_CONSIS_SYNC = 'consistent_synchronized'

View File

@ -125,6 +125,13 @@ storwize_svc_opts = [
default=None,
help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'),
cfg.StrOpt('storwize_peer_pool',
default=None,
help='Specifies the name of the peer pool for hyperswap '
'volume, the peer pool must exist on the other site.'),
cfg.StrOpt('storwize_preferred_host_site',
default=None,
help='Specifies the preferred host site name.'),
cfg.IntOpt('cycle_period_seconds',
default=300,
min=60, max=86400,
@ -237,9 +244,11 @@ class StorwizeSSH(object):
port.append(port_name)
return port
def mkhost(self, host_name, port_type, port_name):
def mkhost(self, host_name, port_type, port_name, site=None):
port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'mkhost', '-force'] + port
if site:
ssh_cmd += ['-site', '"%s"' % site]
ssh_cmd += ['-name', '"%s"' % host_name]
return self.run_ssh_check_created(ssh_cmd)
@ -261,6 +270,10 @@ class StorwizeSSH(object):
log_cmd = 'svctask chhost -chapsecret *** %s' % host
self.run_ssh_assert_no_output(ssh_cmd, log_cmd)
def chhost(self, host, site):
ssh_cmd = ['svctask', 'chhost', '-site', '"%s"' % site, '"%s"' % 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)
@ -703,6 +716,29 @@ class StorwizeSSH(object):
copy_id, '-vdisk', vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
def mkvolume(self, name, size, units, pool, params):
ssh_cmd = ['svctask', 'mkvolume', '-name', name, '-pool',
'"%s"' % pool, '-size', size, '-unit', units] + params
return self.run_ssh_check_created(ssh_cmd)
def rmvolume(self, volume, force=True):
ssh_cmd = ['svctask', 'rmvolume']
if force:
ssh_cmd += ['-removehostmappings', '-removefcmaps',
'-removercrelationships']
ssh_cmd += ['"%s"' % volume]
self.run_ssh_assert_no_output(ssh_cmd)
def addvolumecopy(self, name, pool, params):
ssh_cmd = ['svctask', 'addvolumecopy', '-pool',
'"%s"' % pool] + params + ['"%s"' % name]
self.run_ssh_assert_no_output(ssh_cmd)
def rmvolumecopy(self, name, pool):
ssh_cmd = ['svctask', 'rmvolumecopy', '-pool',
'"%s"' % pool, '"%s"' % name]
self.run_ssh_assert_no_output(ssh_cmd)
class StorwizeHelpers(object):
@ -770,6 +806,7 @@ class StorwizeHelpers(object):
raise exception.VolumeBackendAPIException(data=msg)
code_level = match_obj.group().split('.')
return {'code_level': tuple([int(x) for x in code_level]),
'topology': resp['topology'],
'system_name': resp['name'],
'system_id': resp['id']}
@ -815,7 +852,7 @@ class StorwizeHelpers(object):
raise exception.VolumeBackendAPIException(data=msg)
return res
def select_io_group(self, state, opts):
def select_io_group(self, state, opts, pool):
selected_iog = 0
iog_list = StorwizeHelpers._get_valid_requested_io_groups(state, opts)
if len(iog_list) == 0:
@ -824,6 +861,25 @@ class StorwizeHelpers(object):
'I/O groups are %(avail)s.')
% {'iogrp': opts['iogrp'],
'avail': state['available_iogrps']})
site_iogrp = []
pool_data = self.get_pool_attrs(pool)
if 'site_id' in pool_data and pool_data['site_id']:
for node in state['storage_nodes'].values():
if pool_data['site_id'] == node['site_id']:
site_iogrp.append(node['IO_group'])
site_iogrp = list(map(int, site_iogrp))
iog_list = list(set(site_iogrp).intersection(iog_list))
if len(iog_list) == 0:
raise exception.InvalidInput(
reason=_('The storage system topology is hyperswap or '
'stretched, The site_id of pool %(pool)s is '
'%(site_id)s, the available I/O groups on this '
'site is %(site_iogrp)s, but the given I/O'
' group(s) is %(iogrp)s.')
% {'pool': pool, 'site_id': pool_data['site_id'],
'site_iogrp': site_iogrp, 'iogrp': opts['iogrp']})
iog_vdc = self.get_vdisk_count_by_io_group()
LOG.debug("IO group current balance %s", iog_vdc)
min_vdisk_count = iog_vdc[iog_list[0]]
@ -864,6 +920,8 @@ class StorwizeHelpers(object):
node['ipv6'] = []
node['enabled_protocols'] = []
nodes[node['id']] = node
node['site_id'] = (node_data['site_id']
if 'site_id' in node_data else None)
except KeyError:
self.handle_keyerror('lsnode', node_data)
return nodes
@ -1037,7 +1095,7 @@ class StorwizeHelpers(object):
LOG.debug('Leave: get_host_from_connector: host %s.', host_name)
return host_name
def create_host(self, connector, iscsi=False):
def create_host(self, connector, iscsi=False, site=None):
"""Create a new host on the storage system.
We create a host name and associate it with the given connection
@ -1091,7 +1149,8 @@ class StorwizeHelpers(object):
# Create a host with one port
port = ports.pop(0)
self.ssh.mkhost(host_name, port[0], port[1])
# Host site_id is necessary for hyperswap volume.
self.ssh.mkhost(host_name, port[0], port[1], site)
# Add any additional ports to the host
for port in ports:
@ -1101,6 +1160,9 @@ class StorwizeHelpers(object):
{'host': connector['host'], 'host_name': host_name})
return host_name
def update_host(self, host_name, site_name):
self.ssh.chhost(host_name, site=site_name)
def delete_host(self, host_name):
self.ssh.rmhost(host_name)
@ -1184,6 +1246,9 @@ class StorwizeHelpers(object):
'replication': False,
'nofmtdisk': config.storwize_svc_vol_nofmtdisk,
'mirror_pool': config.storwize_svc_mirror_pool,
'volume_topology': None,
'peer_pool': config.storwize_peer_pool,
'host_site': config.storwize_preferred_host_site,
'cycle_period_seconds': config.cycle_period_seconds}
return opt
@ -1426,6 +1491,65 @@ class StorwizeHelpers(object):
self.ssh.mkvdisk(name, size, units, mdiskgrp, opts, params)
LOG.debug('Leave: _create_vdisk: volume %s.', name)
def _get_hyperswap_volume_create_params(self, opts):
# Storwize/svc use cli command mkvolume to create hyperswap volume.
# You must specify -thin with grainsize.
# You must specify either -thin or -compressed with warning.
params = []
LOG.debug('The I/O groups of a hyperswap volume will be selected by '
'storage.')
if opts['rsize'] != -1:
params.extend(['-buffersize', '%s%%' % str(opts['rsize']),
'-warning',
'%s%%' % six.text_type(opts['warning'])])
if not opts['autoexpand']:
params.append('-noautoexpand')
if opts['compression']:
params.append('-compressed')
else:
params.append('-thin')
params.extend(['-grainsize', six.text_type(opts['grainsize'])])
return params
def create_hyperswap_volume(self, vol_name, size, units, pool, opts):
vol_name = '"%s"' % vol_name
params = self._get_hyperswap_volume_create_params(opts)
self.ssh.mkvolume(vol_name, six.text_type(size), units, pool, params)
def convert_volume_to_hyperswap(self, vol_name, opts, state):
vol_name = '%s' % vol_name
if not self.is_system_topology_hyperswap(state):
reason = _('Convert volume to hyperswap failed, the system is '
'below release 7.6.0.0 or it is not hyperswap '
'topology.')
raise exception.VolumeDriverException(reason=reason)
else:
attr = self.get_vdisk_attributes(vol_name)
if attr is None:
msg = (_('convert_volume_to_hyperswap: Failed to get '
'attributes for volume %s.') % vol_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
pool = attr['mdisk_grp_name']
self.check_hyperswap_pool(pool, opts['peer_pool'])
hyper_pool = '%s' % opts['peer_pool']
params = self._get_hyperswap_volume_create_params(opts)
self.ssh.addvolumecopy(vol_name, hyper_pool, params)
def convert_hyperswap_volume_to_normal(self, vol_name, peer_pool):
vol_name = '%s' % vol_name
hyper_pool = '%s' % peer_pool
self.ssh.rmvolumecopy(vol_name, hyper_pool)
def delete_hyperswap_volume(self, volume, force):
"""Ensures that vdisk is not part of FC mapping and deletes it."""
if not self.is_vdisk_defined(volume):
LOG.warning('Tried to delete non-existent volume %s.', volume)
return
self.ensure_vdisk_no_fc_mappings(volume, allow_snaps=True,
allow_fctgt = True)
self.ssh.rmvolume(volume, force=force)
def get_vdisk_attributes(self, vdisk):
attrs = self.ssh.lsvdisk(vdisk)
return attrs
@ -1737,6 +1861,7 @@ class StorwizeHelpers(object):
for map_id in mapping_ids:
attrs = self._get_flashcopy_mapping_attributes(map_id)
# We should ignore GMCV flash copies
# Hyperswap flash copies are also ignored.
if not attrs or 'yes' == attrs['rc_controlled']:
continue
source = attrs['source_vdisk_name']
@ -2094,15 +2219,16 @@ class StorwizeHelpers(object):
self.ssh.chvdisk(vdisk, ['-' + param, value])
def change_vdisk_options(self, vdisk, changes, opts, state):
change_value = {'warning': '', 'easytier': '', 'autoexpand': ''}
if 'warning' in opts:
opts['warning'] = '%s%%' % str(opts['warning'])
change_value['warning'] = '%s%%' % str(opts['warning'])
if 'easytier' in opts:
opts['easytier'] = 'on' if opts['easytier'] else 'off'
change_value['easytier'] = 'on' if opts['easytier'] else 'off'
if 'autoexpand' in opts:
opts['autoexpand'] = 'on' if opts['autoexpand'] else 'off'
change_value['autoexpand'] = 'on' if opts['autoexpand'] else 'off'
for key in changes:
self.ssh.chvdisk(vdisk, ['-' + key, opts[key]])
self.ssh.chvdisk(vdisk, ['-' + key, change_value[key]])
def change_vdisk_iogrp(self, vdisk, state, iogrp):
if state['code_level'] < (6, 4, 0, 0):
@ -2150,6 +2276,60 @@ class StorwizeHelpers(object):
def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
self.ssh.migratevdisk(vdisk, dest_pool, copy_id)
def is_system_topology_hyperswap(self, state):
"""Returns True if the system version higher than 7.5 and the system
topology is hyperswap.
"""
if state['code_level'] < (7, 6, 0, 0):
LOG.debug('Hyperswap failure as the storage'
'code_level is %(code_level)s, below '
'the required 7.6.0.0.',
{'code_level': state['code_level']})
else:
if state['topology'] == 'hyperswap':
return True
else:
LOG.debug('Hyperswap failure as the storage system '
'topology is not hyperswap.')
return False
def check_hyperswap_pool(self, pool, peer_pool):
# Check the hyperswap pools.
if not peer_pool:
raise exception.InvalidInput(
reason=_('The peer pool is necessary for hyperswap volume, '
'please configure the peer pool.'))
pool_attr = self.get_pool_attrs(pool)
peer_pool_attr = self.get_pool_attrs(peer_pool)
if not peer_pool_attr:
raise exception.InvalidInput(
reason=_('The hyperswap peer pool %s '
'is invalid.') % peer_pool)
if not pool_attr['site_id'] or not peer_pool_attr['site_id']:
raise exception.InvalidInput(
reason=_('The site_id of pools is necessary for hyperswap '
'volume, but there is no site_id in the pool or '
'peer pool.'))
if pool_attr['site_id'] == peer_pool_attr['site_id']:
raise exception.InvalidInput(
reason=_('The hyperswap volume must be configured in two '
'independent sites, the pool %(pool)s is on the '
'same site as peer_pool %(peer_pool)s. ') %
{'pool': pool, 'peer_pool': peer_pool})
def is_volume_hyperswap(self, vol_name):
"""Returns True if the volume rcrelationship is activeactive."""
is_hyper_volume = False
vol_attrs = self.get_vdisk_attributes(vol_name)
if vol_attrs and vol_attrs['RC_name']:
relationship = self.ssh.lsrcrelationship(vol_attrs['RC_name'])
if relationship[0]['copy_type'] == 'activeactive':
is_hyper_volume = True
return is_hyper_volume
class CLIResponse(object):
"""Parse SVC CLI output and generate iterable."""
@ -2628,13 +2808,38 @@ class StorwizeSVCCommonDriver(san.SanDriver,
rep_type = self._get_volume_replicated_type(ctxt, volume)
pool = utils.extract_host(volume['host'], 'pool')
if opts['mirror_pool'] and rep_type:
reason = _('Create mirror volume with replication enabled is '
'not supported.')
raise exception.InvalidInput(reason=reason)
opts['iogrp'] = self._helpers.select_io_group(self._state, opts)
self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
model_update = None
if opts['volume_topology'] == 'hyperswap':
LOG.debug('Volume %s to be created is a hyperswap volume.',
volume.name)
if not self._helpers.is_system_topology_hyperswap(self._state):
reason = _('Create hyperswap volume failed, the system is '
'below release 7.6.0.0 or it is not hyperswap '
'topology.')
raise exception.InvalidInput(reason=reason)
if opts['mirror_pool'] or rep_type:
reason = _('Create hyperswap volume with streched cluster or '
'replication enabled is not supported.')
raise exception.InvalidInput(reason=reason)
if not opts['easytier']:
raise exception.InvalidInput(
reason=_('The default easytier of hyperswap volume is '
'on, it does not support easytier off.'))
self._helpers.check_hyperswap_pool(pool, opts['peer_pool'])
hyperpool = '%s:%s' % (pool, opts['peer_pool'])
self._helpers.create_hyperswap_volume(volume.name,
volume.size, 'gb',
hyperpool, opts)
else:
if opts['mirror_pool'] and rep_type:
reason = _('Create mirror volume with replication enabled is '
'not supported.')
raise exception.InvalidInput(reason=reason)
opts['iogrp'] = self._helpers.select_io_group(self._state,
opts, pool)
self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
if opts['qos']:
self._helpers.add_vdisk_qos(volume['name'], opts['qos'])
@ -2657,6 +2862,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.debug('enter: delete_volume: volume %s', volume['name'])
ctxt = context.get_admin_context()
hyper_volume = self._helpers.is_volume_hyperswap(volume.name)
if hyper_volume:
LOG.debug('Volume %s to be deleted is a hyperswap '
'volume.', volume.name)
self._helpers.delete_hyperswap_volume(volume.name, False)
return
rep_type = self._get_volume_replicated_type(ctxt, volume)
if rep_type:
if self._aux_backend_helpers:
@ -2714,6 +2926,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
pool = utils.extract_host(source_vol['host'], 'pool')
opts = self._get_vdisk_params(source_vol['volume_type_id'])
if opts['volume_topology'] == 'hyperswap':
msg = _('create_snapshot: Create snapshot to a '
'hyperswap volume is not allowed.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
self._helpers.create_copy(snapshot['volume_name'], snapshot['name'],
snapshot['volume_id'], self.configuration,
opts, False, pool=pool)
@ -2787,6 +3006,19 @@ class StorwizeSVCCommonDriver(san.SanDriver,
if opts['qos']:
self._helpers.add_vdisk_qos(tgt_volume['name'], opts['qos'])
if opts['volume_topology'] == 'hyperswap':
LOG.debug('The source volume %s to be cloned is a hyperswap '
'volume.', src_volume.name)
# Ensures the vdisk is not part of FC mapping.
# Otherwize convert it to hyperswap volume will be failed.
self._helpers.ensure_vdisk_no_fc_mappings(tgt_volume['name'],
allow_snaps=True,
allow_fctgt=False)
self._helpers.convert_volume_to_hyperswap(tgt_volume['name'],
opts,
self._state)
ctxt = context.get_admin_context()
model_update = {'replication_status':
fields.ReplicationStatus.NOT_CAPABLE}
@ -2806,6 +3038,12 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def _extend_volume_op(self, volume, new_size, old_size=None):
LOG.debug('enter: _extend_volume_op: volume %s', volume['id'])
volume_name = self._get_target_vol(volume)
if self._helpers.is_volume_hyperswap(volume_name):
msg = _('_extend_volume_op: Extending a hyperswap volume is '
'not supported.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
ret = self._helpers.ensure_vdisk_no_fc_mappings(volume_name,
allow_snaps=False)
if not ret:
@ -3930,6 +4168,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s',
{'id': volume['id'], 'host': host['host']})
# hyperswap volume doesn't support migrate
if self._helpers.is_volume_hyperswap(volume['name']):
msg = _('migrate_volume: Migrating a hyperswap volume is '
'not supported.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
false_ret = (False, None)
dest_pool = self._helpers.can_migrate_to_host(host, self._state)
if dest_pool is None:
@ -3948,8 +4193,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.migratevdisk(volume.name, dest_pool,
copies['primary']['copy_id'])
else:
self.add_vdisk_copy(volume.name, dest_pool, vol_type,
auto_delete=True)
self._check_volume_copy_ops()
if self._state['code_level'] < (7, 6, 0, 0):
new_op = self.add_vdisk_copy(volume.name, dest_pool,
vol_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
else:
self.add_vdisk_copy(volume.name, dest_pool, vol_type,
auto_delete=True)
LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s',
{'id': volume.id, 'host': host['host']})
@ -4021,6 +4272,98 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.change_relationship_cycleperiod(volume.name,
new_cps)
def _check_hyperswap_retype_params(self, volume, new_opts, old_opts,
change_mirror, new_rep_type,
old_rep_type, old_pool,
new_pool, old_io_grp):
if new_opts['mirror_pool'] or old_opts['mirror_pool']:
msg = (_('Unable to retype volume %s: current action needs '
'volume-copy, it is not allowed for hyperswap '
'type.') % volume.name)
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if new_rep_type or old_rep_type:
msg = _('Retype between replicated volume and hyperswap volume'
' is not allowed.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if (old_io_grp not in
StorwizeHelpers._get_valid_requested_io_groups(
self._state, new_opts)):
msg = _('Unable to retype: it is not allowed to change '
'hyperswap type and IO group at the same time.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if new_opts['volume_topology'] == 'hyperswap':
if old_pool != new_pool:
msg = (_('Unable to retype volume %s: current action needs '
'volume pool change, hyperswap volume does not '
'support pool change.') % volume.name)
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if volume.previous_status == 'in-use':
msg = _('Retype an in-use volume to a hyperswap '
'volume is not allowed.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if not new_opts['easytier']:
raise exception.InvalidInput(
reason=_('The default easytier of hyperswap volume is '
'on, it does not support easytier off.'))
if (old_opts['volume_topology'] != 'hyperswap' and
self._helpers._get_vdisk_fc_mappings(volume.name)):
msg = _('Unable to retype: it is not allowed to change a '
'normal volume with snapshot to a hyperswap '
'volume.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
if (old_opts['volume_topology'] == 'hyperswap' and
old_opts['peer_pool'] != new_opts['peer_pool']):
msg = _('Unable to retype: it is not allowed to change a '
'hyperswap volume peer_pool.')
LOG.error(msg)
raise exception.InvalidInput(message=msg)
def _retype_hyperswap_volume(self, volume, host, old_opts, new_opts,
old_pool, new_pool, vdisk_changes,
need_copy, new_type):
if (old_opts['volume_topology'] != 'hyperswap' and
new_opts['volume_topology'] == 'hyperswap'):
LOG.debug('retype: Convert a normal volume %s to hyperswap '
'volume.', volume.name)
self._helpers.convert_volume_to_hyperswap(volume.name,
new_opts,
self._state)
elif (old_opts['volume_topology'] == 'hyperswap' and
new_opts['volume_topology'] != 'hyperswap'):
LOG.debug('retype: Convert a hyperswap volume %s to normal '
'volume.', volume.name)
if new_pool == old_pool:
self._helpers.convert_hyperswap_volume_to_normal(
volume.name,
old_opts['peer_pool'])
elif new_pool == old_opts['peer_pool']:
self._helpers.convert_hyperswap_volume_to_normal(
volume.name,
old_pool)
else:
rel_info = self._helpers.get_relationship_info(volume.name)
aux_vdisk = rel_info['aux_vdisk_name']
if need_copy:
self.add_vdisk_copy(aux_vdisk, old_opts['peer_pool'], new_type,
auto_delete=True)
elif vdisk_changes:
self._helpers.change_vdisk_options(aux_vdisk,
vdisk_changes,
new_opts, self._state)
if need_copy:
self.add_vdisk_copy(volume.name, old_pool, new_type,
auto_delete=True)
elif vdisk_changes:
self._helpers.change_vdisk_options(volume.name,
vdisk_changes,
new_opts, self._state)
def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type.
@ -4066,8 +4409,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
elif key in no_copy_keys:
vdisk_changes.append(key)
if (utils.extract_host(volume['host'], 'pool') !=
utils.extract_host(host['host'], 'pool')):
old_pool = utils.extract_host(volume['host'], 'pool')
new_pool = utils.extract_host(host['host'], 'pool')
if old_pool != new_pool:
need_copy = True
if old_opts['mirror_pool'] != new_opts['mirror_pool']:
@ -4078,50 +4422,69 @@ class StorwizeSVCCommonDriver(san.SanDriver,
new_rep_type = self._get_specs_replicated_type(new_type)
old_rep_type = self._get_volume_replicated_type(ctxt, volume)
old_io_grp = self._helpers.get_volume_io_group(volume['name'])
new_io_grp = self._helpers.select_io_group(self._state, new_opts)
new_io_grp = self._helpers.select_io_group(self._state,
new_opts, new_pool)
self._verify_retype_params(volume, new_opts, old_opts, need_copy,
change_mirror, new_rep_type, old_rep_type)
if need_copy:
self._check_volume_copy_ops()
dest_pool = self._helpers.can_migrate_to_host(host, self._state)
if dest_pool is None:
return False
retype_iogrp_property(volume,
new_io_grp, old_io_grp)
try:
self.add_vdisk_copy(volume['name'], dest_pool, new_type,
auto_delete=True)
except exception.VolumeDriverException:
# roll back changing iogrp property
retype_iogrp_property(volume, old_io_grp, new_io_grp)
msg = (_('Unable to retype: A copy of volume %s exists. '
'Retyping would exceed the limit of 2 copies.'),
volume['id'])
raise exception.VolumeDriverException(message=msg)
if old_opts['volume_topology'] or new_opts['volume_topology']:
self._check_hyperswap_retype_params(volume, new_opts, old_opts,
change_mirror, new_rep_type,
old_rep_type, old_pool,
new_pool, old_io_grp)
self._retype_hyperswap_volume(volume, host, old_opts, new_opts,
old_pool, new_pool, vdisk_changes,
need_copy, new_type)
else:
retype_iogrp_property(volume, new_io_grp, old_io_grp)
if need_copy:
self._check_volume_copy_ops()
dest_pool = self._helpers.can_migrate_to_host(host,
self._state)
if dest_pool is None:
return False
self._helpers.change_vdisk_options(volume['name'], vdisk_changes,
new_opts, self._state)
if change_mirror:
copies = self._helpers.get_vdisk_copies(volume.name)
if not old_opts['mirror_pool'] and new_opts['mirror_pool']:
# retype from non mirror vol to mirror vol
self.add_vdisk_copy(volume['name'],
new_opts['mirror_pool'], new_type)
elif old_opts['mirror_pool'] and not new_opts['mirror_pool']:
# retype from mirror vol to non mirror vol
secondary = copies['secondary']
if secondary:
self._helpers.rm_vdisk_copy(
volume.name, secondary['copy_id'])
else:
# migrate the second copy to another pool.
self._helpers.migratevdisk(
volume.name, new_opts['mirror_pool'],
copies['secondary']['copy_id'])
retype_iogrp_property(volume,
new_io_grp, old_io_grp)
try:
if self._state['code_level'] < (7, 6, 0, 0):
new_op = self.add_vdisk_copy(volume.name, dest_pool,
new_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
else:
self.add_vdisk_copy(volume.name, dest_pool, new_type,
auto_delete=True)
except exception.VolumeDriverException:
# roll back changing iogrp property
retype_iogrp_property(volume, old_io_grp, new_io_grp)
msg = (_('Unable to retype: A copy of volume %s exists. '
'Retyping would exceed the limit of 2 copies.'),
volume['id'])
raise exception.VolumeDriverException(message=msg)
else:
retype_iogrp_property(volume, new_io_grp, old_io_grp)
self._helpers.change_vdisk_options(volume['name'],
vdisk_changes,
new_opts, self._state)
if change_mirror:
copies = self._helpers.get_vdisk_copies(volume.name)
if not old_opts['mirror_pool'] and new_opts['mirror_pool']:
# retype from non mirror vol to mirror vol
self.add_vdisk_copy(volume['name'],
new_opts['mirror_pool'], new_type)
elif (old_opts['mirror_pool'] and
not new_opts['mirror_pool']):
# retype from mirror vol to non mirror vol
secondary = copies['secondary']
if secondary:
self._helpers.rm_vdisk_copy(
volume.name, secondary['copy_id'])
else:
# migrate the second copy to another pool.
self._helpers.migratevdisk(
volume.name, new_opts['mirror_pool'],
copies['secondary']['copy_id'])
if new_opts['qos']:
# Add the new QoS setting to the volume. If the volume has an
# old QoS setting, it will be overwritten.
@ -4222,7 +4585,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
vol_rep_type = None
rel_info = self._helpers.get_relationship_info(vdisk['name'])
copies = self._helpers.get_vdisk_copies(vdisk['name'])
if rel_info:
if rel_info and rel_info['copy_type'] != 'activeactive':
vol_rep_type = (
storwize_const.GMCV if
storwize_const.GMCV_MULTI == rel_info['cycling_mode']
@ -4264,6 +4627,34 @@ class StorwizeSVCCommonDriver(san.SanDriver,
opts = self._get_vdisk_params(volume['volume_type_id'],
volume_metadata=
volume.get('volume_metadata'))
# Manage hyperswap volume
if rel_info and rel_info['copy_type'] == 'activeactive':
if opts['volume_topology'] != 'hyperswap':
msg = _("Failed to manage existing volume due to "
"the hyperswap volume to be managed is "
"mismatched with the provided non-hyperswap type.")
raise exception.ManageExistingVolumeTypeMismatch(
reason=msg)
aux_vdisk = rel_info['aux_vdisk_name']
aux_vol_attr = self._helpers.get_vdisk_attributes(aux_vdisk)
peer_pool = aux_vol_attr['mdisk_grp_name']
if opts['peer_pool'] != peer_pool:
msg = (_("Failed to manage existing hyperswap volume due "
"to peer pool mismatch. The peer pool of the "
"volume to be managed is %(vol_pool)s, but the "
"peer_pool of the chosen type is %(peer_pool)s.")
% {'vol_pool': peer_pool,
'peer_pool': opts['peer_pool']})
raise exception.ManageExistingVolumeTypeMismatch(
reason=msg)
else:
if opts['volume_topology'] == 'hyperswap':
msg = _("Failed to manage existing volume, the volume to "
"be managed is not a hyperswap volume, "
"mismatch with the provided hyperswap type.")
raise exception.ManageExistingVolumeTypeMismatch(
reason=msg)
resp = self._helpers.lsvdiskcopy(vdisk['name'])
expected_copy_num = 2 if opts['mirror_pool'] else 1
if len(resp) != expected_copy_num:
@ -4318,7 +4709,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
msg = (_("Failed to manage existing volume due to "
"I/O group mismatch. The I/O group of the "
"volume to be managed is %(vdisk_iogrp)s. I/O group"
"of the chosen type is %(opt_iogrp)s.") %
" of the chosen type is %(opt_iogrp)s.") %
{'vdisk_iogrp': vdisk['IO_group_name'],
'opt_iogrp': opts['iogrp']})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
@ -4393,9 +4784,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return self._stats
@staticmethod
def _get_rccg_name(group, grp_id=None):
def _get_rccg_name(group, grp_id=None, hyper_grp=False):
group_id = group.id if group else grp_id
return storwize_const.RCCG_PREFIX + group_id[0:4] + '-' + group_id[-5:]
rccg = (storwize_const.HYPERCG_PREFIX
if hyper_grp else storwize_const.RCCG_PREFIX)
return rccg + group_id[0:4] + '-' + group_id[-5:]
# Add CG capability to generic volume groups
def create_group(self, context, group):
@ -4405,7 +4798,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
:param group: the group object.
:returns: model_update
"""
LOG.debug("Creating group.")
model_update = {'status': fields.GroupStatus.AVAILABLE}
@ -4418,7 +4810,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
support_grps = ['group_snapshot_enabled',
'consistent_group_snapshot_enabled',
'consistent_group_replication_enabled']
'consistent_group_replication_enabled',
'hyperswap_group_enabled']
supported_grp = False
for grp_spec in support_grps:
if utils.is_group_a_type(group, grp_spec):
@ -4442,6 +4835,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'not supported.')
model_update = {'status': fields.GroupStatus.ERROR}
return model_update
opts = self._get_vdisk_params(vol_type_id)
if opts['volume_topology']:
# An unsupported configuration
LOG.error('Unable to create group: create consistent '
'snapshot group with a hyperswap volume type'
' is not supported.')
model_update = {'status': fields.GroupStatus.ERROR}
return model_update
# We'll rely on the generic group implementation if it is
# a non-consistent snapshot group.
@ -4489,6 +4890,33 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"Exception: %(exception)s.",
{'rccg': rccg_name, 'exception': err})
model_update = {'status': fields.GroupStatus.ERROR}
return model_update
if utils.is_group_a_type(group, "hyperswap_group_enabled"):
if not self._helpers.is_system_topology_hyperswap(self._state):
LOG.error('Unable to create group: create group on '
'a system that does not support hyperswap.')
model_update = {'status': fields.GroupStatus.ERROR}
for vol_type_id in group.volume_type_ids:
opts = self._get_vdisk_params(vol_type_id)
if not opts['volume_topology']:
# An unsupported configuration
LOG.error('Unable to create group: create consistent '
'hyperswap group with non-hyperswap volume'
' type is not supported.')
model_update = {'status': fields.GroupStatus.ERROR}
return model_update
rccg_name = self._get_rccg_name(group, hyper_grp=True)
try:
self._helpers.create_rccg(
rccg_name, self._state['system_name'])
except exception.VolumeBackendAPIException as err:
LOG.error("Failed to create rccg %(rccg)s. "
"Exception: %(exception)s.",
{'rccg': group.name, 'exception': err})
model_update = {'status': fields.GroupStatus.ERROR}
return model_update
def delete_group(self, context, group, volumes):
@ -4503,10 +4931,12 @@ class StorwizeSVCCommonDriver(san.SanDriver,
# we'll rely on the generic group implementation if it is
# not a consistency group and not a consistency replication
# request.
# request and not a hyperswap group request.
if (not utils.is_group_a_cg_snapshot_type(group) and not
utils.is_group_a_type(group,
"consistent_group_replication_enabled")):
utils.is_group_a_type(group,
"consistent_group_replication_enabled")
and not utils.is_group_a_type(group,
"hyperswap_group_enabled")):
raise NotImplementedError()
model_update = {'status': fields.GroupStatus.DELETED}
@ -4515,6 +4945,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"consistent_group_replication_enabled"):
model_update, volumes_model_update = self._delete_replication_grp(
group, volumes)
if utils.is_group_a_type(group, "hyperswap_group_enabled"):
model_update, volumes_model_update = self._delete_hyperswap_grp(
group, volumes)
else:
for volume in volumes:
try:
@ -4547,10 +4982,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.debug("Updating group.")
# we'll rely on the generic group implementation if it is not a
# consistency group request and not consistency replication request.
# consistency group request and not consistency replication request
# and not a hyperswap group request.
if (not utils.is_group_a_cg_snapshot_type(group) and not
utils.is_group_a_type(group,
"consistent_group_replication_enabled")):
utils.is_group_a_type(group,
"consistent_group_replication_enabled")
and not utils.is_group_a_type(group,
"hyperswap_group_enabled")):
raise NotImplementedError()
if utils.is_group_a_type(group,
@ -4558,6 +4996,10 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return self._update_replication_grp(context, group, add_volumes,
remove_volumes)
if utils.is_group_a_type(group, "hyperswap_group_enabled"):
return self._update_hyperswap_group(context, group,
add_volumes, remove_volumes)
if utils.is_group_a_cg_snapshot_type(group):
return None, None, None
@ -4585,6 +5027,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
if utils.is_group_a_type(group, "hyperswap_group_enabled"):
# An unsupported configuration
msg = _('Unable to create hyperswap group: create hyperswap '
'group from a hyperswap group is not supported.')
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not utils.is_group_a_cg_snapshot_type(group):
# we'll rely on the generic volume groups implementation if it is
# not a consistency group request.
@ -4929,3 +5378,82 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"from group. Exception: %(exception)s.",
{'vol': volume.name, 'exception': err})
return model_update, None, None
def _delete_hyperswap_grp(self, group, volumes):
model_update = {'status': fields.GroupStatus.DELETED}
volumes_model_update = []
try:
rccg_name = self._get_rccg_name(group, hyper_grp=True)
self._helpers.delete_rccg(rccg_name)
except exception.VolumeBackendAPIException as err:
LOG.error("Failed to delete rccg %(rccg)s. "
"Exception: %(exception)s.",
{'rccg': group.name, 'exception': err})
model_update = {'status': fields.GroupStatus.ERROR_DELETING}
for volume in volumes:
try:
self._helpers.delete_hyperswap_volume(volume.name, True)
volumes_model_update.append(
{'id': volume.id, 'status': 'deleted'})
except exception.VolumeDriverException as err:
LOG.error("Failed to delete the volume %(vol)s of CG. "
"Exception: %(exception)s.",
{'vol': volume.name, 'exception': err})
volumes_model_update.append(
{'id': volume.id,
'status': 'error_deleting'})
return model_update, volumes_model_update
def _update_hyperswap_group(self, context, group,
add_volumes=None, remove_volumes=None):
LOG.info("Update hyperswap group: %(group)s. ", {'group': group.id})
model_update = {'status': fields.GroupStatus.AVAILABLE}
rccg_name = self._get_rccg_name(group, hyper_grp=True)
if not self._helpers.get_rccg(rccg_name):
LOG.error("Failed to update rccg: %(grp)s does not exist in "
"backend.", {'grp': group.id})
model_update['status'] = fields.GroupStatus.ERROR
return model_update, None, None
# Add remote copy relationship to rccg
for volume in add_volumes:
hyper_volume = self._helpers.is_volume_hyperswap(volume.name)
if not hyper_volume:
LOG.error("Failed to update rccg: the non hyperswap volume"
" of %(vol)s can't be added to hyperswap group.",
{'vol': volume.id})
model_update['status'] = fields.GroupStatus.ERROR
return model_update, None, None
try:
rcrel = self._helpers.get_relationship_info(volume.name)
if not rcrel:
LOG.error("Failed to update rccg: remote copy relationship"
" of %(vol)s does not exist in backend.",
{'vol': volume.id})
model_update['status'] = fields.GroupStatus.ERROR
else:
self._helpers.chrcrelationship(rcrel['name'], rccg_name)
except exception.VolumeBackendAPIException as err:
model_update['status'] = fields.GroupStatus.ERROR
LOG.error("Failed to add the remote copy of volume %(vol)s to "
"rccg. Exception: %(exception)s.",
{'vol': volume.name, 'exception': err})
# Remove remote copy relationship from rccg
for volume in remove_volumes:
try:
rcrel = self._helpers.get_relationship_info(volume.name)
if not rcrel:
LOG.error("Failed to update rccg: remote copy relationship"
" of %(vol)s does not exit in backend.",
{'vol': volume.id})
model_update['status'] = fields.GroupStatus.ERROR
else:
self._helpers.chrcrelationship(rcrel['name'])
except exception.VolumeBackendAPIException as err:
model_update['status'] = fields.GroupStatus.ERROR
LOG.error("Failed to remove the remote copy of volume %(vol)s "
"from rccg. Exception: %(exception)s.",
{'vol': volume.name, 'exception': err})
return model_update, None, None

View File

@ -95,9 +95,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
2.2.2 - Add npiv support
2.2.3 - Add replication group support
2.2.4 - Add backup snapshots support
2.2.5 - Add hyperswap support
"""
VERSION = "2.2.3"
VERSION = "2.2.5"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -123,10 +124,11 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
# attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot)
vol_attrs = ['id', 'name', 'display_name']
vol_attrs = ['id', 'name', 'volume_type_id', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
volume_type_id=snapshot.volume_type_id,
display_name='backup-snapshot')
return self.initialize_connection(volume, connector)
@ -163,12 +165,36 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
else:
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
opts = self._get_vdisk_params(volume.volume_type_id)
host_site = opts['host_site']
# Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector)
if host_name is None:
# Host does not exist - add a new host to Storwize/SVC
host_name = backend_helper.create_host(connector)
# The host_site is necessary for hyperswap volume.
if self._helpers.is_volume_hyperswap(
volume_name) and host_site is None:
msg = (_('There is no host_site configured for a hyperswap'
' volume %s.') % volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
host_name = backend_helper.create_host(connector, host_site)
else:
host_info = self._helpers.ssh.lshost(host=host_name)
if 'site_name' in host_info[0]:
if not host_info[0]['site_name'] and host_site:
self._helpers.update_host(host_name, host_site)
elif host_info[0]['site_name']:
ref_host_site = host_info[0]['site_name']
if host_site and host_site != ref_host_site:
msg = (_('The existing host site is %(ref_host_site)s,'
' but the new host site is %(host_site)s.') %
{'ref_host_site': ref_host_site,
'host_site': host_site})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
volume_attributes = backend_helper.get_vdisk_attributes(volume_name)
if volume_attributes is None:

View File

@ -95,9 +95,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
2.2.1 - Add vdisk mirror/stretch cluster support
2.2.2 - Add replication group support
2.2.3 - Add backup snapshots support
2.2.4 - Add hyperswap support
"""
VERSION = "2.2.2"
VERSION = "2.2.4"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -123,10 +124,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot)
vol_attrs = ['id', 'name', 'display_name']
vol_attrs = ['id', 'name', 'volume_type_id', 'display_name']
Volume = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id,
name=snapshot.name,
volume_type_id=snapshot.volume_type_id,
display_name='backup-snapshot')
return self.initialize_connection(volume, connector)
@ -161,13 +163,38 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
else:
volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume)
opts = self._get_vdisk_params(volume.volume_type_id)
host_site = opts['host_site']
# Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector,
iscsi=True)
if host_name is None:
# Host does not exist - add a new host to Storwize/SVC
host_name = backend_helper.create_host(connector, iscsi=True)
# The host_site is necessary for hyperswap volume
if self._helpers.is_volume_hyperswap(
volume_name) and host_site is None:
msg = (_('There is no host_site configured for a hyperswap'
' volume %s.') % volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
host_name = backend_helper.create_host(connector, iscsi=True,
site = host_site)
else:
host_info = self._helpers.ssh.lshost(host=host_name)
if 'site_name' in host_info[0]:
if not host_info[0]['site_name'] and host_site:
self._helpers.update_host(host_name, host_site)
elif host_info[0]['site_name']:
ref_host_site = host_info[0]['site_name']
if host_site and host_site != ref_host_site:
msg = (_('The existing host site is %(ref_host_site)s,'
' but the new host site is %(host_site)s.') %
{'ref_host_site': ref_host_site,
'host_site': host_site})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
chap_secret = backend_helper.get_chap_secret_for_host(host_name)
chap_enabled = self.configuration.storwize_svc_iscsi_chap_enabled

View File

@ -315,6 +315,9 @@ driver:
- multipath
- iogrp
- mirror_pool
- volume_topology
- peer_pool
- host_site
These keys have the same semantics as their counterparts in the
configuration file. They are set similarly; for example, ``rsize=2`` or
@ -452,6 +455,12 @@ modify volume types, you can also change these extra specs properties:
- mirror_pool
- volume_topology
- peer_pool
- host_site
.. note::
When you change the ``rsize``, ``grainsize`` or ``compression``
@ -515,3 +524,26 @@ default as the ``backend_id``:
If the synchronization is not done manually, Storwize Block Storage
service driver will perform the synchronization and do the failback
after the synchronization is finished.
Hyperswap Volumes
-----------------
A hyperswap volume is created with a volume-type that has the extra spec
``drivers:volume_topology`` set to ``hyperswap``.
To support hyperswap volumes, IBM Storwize/SVC firmware version 7.6.0 or
later is required.
.. code-block:: console
$ cinder type-create hyper_type
$ cinder type-key hyper_type set drivers:volume_topology=hyperswap \
drivers:peer_pool=Pool_site2 drivers:host_site=site1
.. note::
The property ``rsize`` is considered as ``buffersize`` for hyperswap
volume.
The hyperswap property ``iogrp`` is selected by storage.
A group is created as a hyperswap group with a group-type that has the
group spec ``hyperswap_group_enabled`` set to ``<is> True``.

View File

@ -0,0 +1,5 @@
---
features:
- Added hyperswap volume and group support in Storwize cinder driver.
Storwize/svc versions prior to 7.6 do not support this feature.