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_' REPLICA_CHG_VOL_PREFIX = 'chg_'
RCCG_PREFIX = 'rccg-' RCCG_PREFIX = 'rccg-'
HYPERCG_PREFIX = 'hycg-'
# remote mirror copy status # remote mirror copy status
REP_CONSIS_SYNC = 'consistent_synchronized' REP_CONSIS_SYNC = 'consistent_synchronized'

View File

@ -125,6 +125,13 @@ storwize_svc_opts = [
default=None, default=None,
help='Specifies the name of the pool in which mirrored copy ' help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'), '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', cfg.IntOpt('cycle_period_seconds',
default=300, default=300,
min=60, max=86400, min=60, max=86400,
@ -237,9 +244,11 @@ class StorwizeSSH(object):
port.append(port_name) port.append(port_name)
return port 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) port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'mkhost', '-force'] + port ssh_cmd = ['svctask', 'mkhost', '-force'] + port
if site:
ssh_cmd += ['-site', '"%s"' % site]
ssh_cmd += ['-name', '"%s"' % host_name] ssh_cmd += ['-name', '"%s"' % host_name]
return self.run_ssh_check_created(ssh_cmd) return self.run_ssh_check_created(ssh_cmd)
@ -261,6 +270,10 @@ class StorwizeSSH(object):
log_cmd = 'svctask chhost -chapsecret *** %s' % host log_cmd = 'svctask chhost -chapsecret *** %s' % host
self.run_ssh_assert_no_output(ssh_cmd, log_cmd) 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): def lsiscsiauth(self):
ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!'] ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True) return self.run_ssh_info(ssh_cmd, with_header=True)
@ -703,6 +716,29 @@ class StorwizeSSH(object):
copy_id, '-vdisk', vdisk] copy_id, '-vdisk', vdisk]
self.run_ssh_assert_no_output(ssh_cmd) 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): class StorwizeHelpers(object):
@ -770,6 +806,7 @@ class StorwizeHelpers(object):
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
code_level = match_obj.group().split('.') code_level = match_obj.group().split('.')
return {'code_level': tuple([int(x) for x in code_level]), return {'code_level': tuple([int(x) for x in code_level]),
'topology': resp['topology'],
'system_name': resp['name'], 'system_name': resp['name'],
'system_id': resp['id']} 'system_id': resp['id']}
@ -815,7 +852,7 @@ class StorwizeHelpers(object):
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
return res return res
def select_io_group(self, state, opts): def select_io_group(self, state, opts, pool):
selected_iog = 0 selected_iog = 0
iog_list = StorwizeHelpers._get_valid_requested_io_groups(state, opts) iog_list = StorwizeHelpers._get_valid_requested_io_groups(state, opts)
if len(iog_list) == 0: if len(iog_list) == 0:
@ -824,6 +861,25 @@ class StorwizeHelpers(object):
'I/O groups are %(avail)s.') 'I/O groups are %(avail)s.')
% {'iogrp': opts['iogrp'], % {'iogrp': opts['iogrp'],
'avail': state['available_iogrps']}) '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() iog_vdc = self.get_vdisk_count_by_io_group()
LOG.debug("IO group current balance %s", iog_vdc) LOG.debug("IO group current balance %s", iog_vdc)
min_vdisk_count = iog_vdc[iog_list[0]] min_vdisk_count = iog_vdc[iog_list[0]]
@ -864,6 +920,8 @@ class StorwizeHelpers(object):
node['ipv6'] = [] node['ipv6'] = []
node['enabled_protocols'] = [] node['enabled_protocols'] = []
nodes[node['id']] = node nodes[node['id']] = node
node['site_id'] = (node_data['site_id']
if 'site_id' in node_data else None)
except KeyError: except KeyError:
self.handle_keyerror('lsnode', node_data) self.handle_keyerror('lsnode', node_data)
return nodes return nodes
@ -1037,7 +1095,7 @@ class StorwizeHelpers(object):
LOG.debug('Leave: get_host_from_connector: host %s.', host_name) LOG.debug('Leave: get_host_from_connector: host %s.', host_name)
return 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. """Create a new host on the storage system.
We create a host name and associate it with the given connection 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 # Create a host with one port
port = ports.pop(0) 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 # Add any additional ports to the host
for port in ports: for port in ports:
@ -1101,6 +1160,9 @@ class StorwizeHelpers(object):
{'host': connector['host'], 'host_name': host_name}) {'host': connector['host'], 'host_name': host_name})
return 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): def delete_host(self, host_name):
self.ssh.rmhost(host_name) self.ssh.rmhost(host_name)
@ -1184,6 +1246,9 @@ class StorwizeHelpers(object):
'replication': False, 'replication': False,
'nofmtdisk': config.storwize_svc_vol_nofmtdisk, 'nofmtdisk': config.storwize_svc_vol_nofmtdisk,
'mirror_pool': config.storwize_svc_mirror_pool, '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} 'cycle_period_seconds': config.cycle_period_seconds}
return opt return opt
@ -1426,6 +1491,65 @@ class StorwizeHelpers(object):
self.ssh.mkvdisk(name, size, units, mdiskgrp, opts, params) self.ssh.mkvdisk(name, size, units, mdiskgrp, opts, params)
LOG.debug('Leave: _create_vdisk: volume %s.', name) 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): def get_vdisk_attributes(self, vdisk):
attrs = self.ssh.lsvdisk(vdisk) attrs = self.ssh.lsvdisk(vdisk)
return attrs return attrs
@ -1737,6 +1861,7 @@ class StorwizeHelpers(object):
for map_id in mapping_ids: for map_id in mapping_ids:
attrs = self._get_flashcopy_mapping_attributes(map_id) attrs = self._get_flashcopy_mapping_attributes(map_id)
# We should ignore GMCV flash copies # We should ignore GMCV flash copies
# Hyperswap flash copies are also ignored.
if not attrs or 'yes' == attrs['rc_controlled']: if not attrs or 'yes' == attrs['rc_controlled']:
continue continue
source = attrs['source_vdisk_name'] source = attrs['source_vdisk_name']
@ -2094,15 +2219,16 @@ class StorwizeHelpers(object):
self.ssh.chvdisk(vdisk, ['-' + param, value]) self.ssh.chvdisk(vdisk, ['-' + param, value])
def change_vdisk_options(self, vdisk, changes, opts, state): def change_vdisk_options(self, vdisk, changes, opts, state):
change_value = {'warning': '', 'easytier': '', 'autoexpand': ''}
if 'warning' in opts: if 'warning' in opts:
opts['warning'] = '%s%%' % str(opts['warning']) change_value['warning'] = '%s%%' % str(opts['warning'])
if 'easytier' in opts: 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: 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: 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): def change_vdisk_iogrp(self, vdisk, state, iogrp):
if state['code_level'] < (6, 4, 0, 0): if state['code_level'] < (6, 4, 0, 0):
@ -2150,6 +2276,60 @@ class StorwizeHelpers(object):
def migratevdisk(self, vdisk, dest_pool, copy_id='0'): def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
self.ssh.migratevdisk(vdisk, dest_pool, copy_id) 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): class CLIResponse(object):
"""Parse SVC CLI output and generate iterable.""" """Parse SVC CLI output and generate iterable."""
@ -2628,13 +2808,38 @@ class StorwizeSVCCommonDriver(san.SanDriver,
rep_type = self._get_volume_replicated_type(ctxt, volume) rep_type = self._get_volume_replicated_type(ctxt, volume)
pool = utils.extract_host(volume['host'], 'pool') pool = utils.extract_host(volume['host'], 'pool')
if opts['mirror_pool'] and rep_type: model_update = None
reason = _('Create mirror volume with replication enabled is '
'not supported.') if opts['volume_topology'] == 'hyperswap':
raise exception.InvalidInput(reason=reason) LOG.debug('Volume %s to be created is a hyperswap volume.',
opts['iogrp'] = self._helpers.select_io_group(self._state, opts) volume.name)
self._helpers.create_vdisk(volume['name'], str(volume['size']), if not self._helpers.is_system_topology_hyperswap(self._state):
'gb', pool, opts) 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']: if opts['qos']:
self._helpers.add_vdisk_qos(volume['name'], 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']) LOG.debug('enter: delete_volume: volume %s', volume['name'])
ctxt = context.get_admin_context() 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) rep_type = self._get_volume_replicated_type(ctxt, volume)
if rep_type: if rep_type:
if self._aux_backend_helpers: if self._aux_backend_helpers:
@ -2714,6 +2926,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
pool = utils.extract_host(source_vol['host'], 'pool') pool = utils.extract_host(source_vol['host'], 'pool')
opts = self._get_vdisk_params(source_vol['volume_type_id']) 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'], self._helpers.create_copy(snapshot['volume_name'], snapshot['name'],
snapshot['volume_id'], self.configuration, snapshot['volume_id'], self.configuration,
opts, False, pool=pool) opts, False, pool=pool)
@ -2787,6 +3006,19 @@ class StorwizeSVCCommonDriver(san.SanDriver,
if opts['qos']: if opts['qos']:
self._helpers.add_vdisk_qos(tgt_volume['name'], 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() ctxt = context.get_admin_context()
model_update = {'replication_status': model_update = {'replication_status':
fields.ReplicationStatus.NOT_CAPABLE} fields.ReplicationStatus.NOT_CAPABLE}
@ -2806,6 +3038,12 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def _extend_volume_op(self, volume, new_size, old_size=None): def _extend_volume_op(self, volume, new_size, old_size=None):
LOG.debug('enter: _extend_volume_op: volume %s', volume['id']) LOG.debug('enter: _extend_volume_op: volume %s', volume['id'])
volume_name = self._get_target_vol(volume) 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, ret = self._helpers.ensure_vdisk_no_fc_mappings(volume_name,
allow_snaps=False) allow_snaps=False)
if not ret: if not ret:
@ -3930,6 +4168,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s', LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s',
{'id': volume['id'], 'host': host['host']}) {'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) false_ret = (False, None)
dest_pool = self._helpers.can_migrate_to_host(host, self._state) dest_pool = self._helpers.can_migrate_to_host(host, self._state)
if dest_pool is None: if dest_pool is None:
@ -3948,8 +4193,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.migratevdisk(volume.name, dest_pool, self._helpers.migratevdisk(volume.name, dest_pool,
copies['primary']['copy_id']) copies['primary']['copy_id'])
else: else:
self.add_vdisk_copy(volume.name, dest_pool, vol_type, self._check_volume_copy_ops()
auto_delete=True) 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', LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s',
{'id': volume.id, 'host': host['host']}) {'id': volume.id, 'host': host['host']})
@ -4021,6 +4272,98 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.change_relationship_cycleperiod(volume.name, self._helpers.change_relationship_cycleperiod(volume.name,
new_cps) 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): def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type. """Convert the volume to be of the new type.
@ -4066,8 +4409,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
elif key in no_copy_keys: elif key in no_copy_keys:
vdisk_changes.append(key) vdisk_changes.append(key)
if (utils.extract_host(volume['host'], 'pool') != old_pool = utils.extract_host(volume['host'], 'pool')
utils.extract_host(host['host'], 'pool')): new_pool = utils.extract_host(host['host'], 'pool')
if old_pool != new_pool:
need_copy = True need_copy = True
if old_opts['mirror_pool'] != new_opts['mirror_pool']: 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) new_rep_type = self._get_specs_replicated_type(new_type)
old_rep_type = self._get_volume_replicated_type(ctxt, volume) old_rep_type = self._get_volume_replicated_type(ctxt, volume)
old_io_grp = self._helpers.get_volume_io_group(volume['name']) 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, self._verify_retype_params(volume, new_opts, old_opts, need_copy,
change_mirror, new_rep_type, old_rep_type) 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, if old_opts['volume_topology'] or new_opts['volume_topology']:
new_io_grp, old_io_grp) self._check_hyperswap_retype_params(volume, new_opts, old_opts,
try: change_mirror, new_rep_type,
self.add_vdisk_copy(volume['name'], dest_pool, new_type, old_rep_type, old_pool,
auto_delete=True) new_pool, old_io_grp)
except exception.VolumeDriverException: self._retype_hyperswap_volume(volume, host, old_opts, new_opts,
# roll back changing iogrp property old_pool, new_pool, vdisk_changes,
retype_iogrp_property(volume, old_io_grp, new_io_grp) need_copy, new_type)
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: 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, retype_iogrp_property(volume,
new_opts, self._state) new_io_grp, old_io_grp)
if change_mirror: try:
copies = self._helpers.get_vdisk_copies(volume.name) if self._state['code_level'] < (7, 6, 0, 0):
if not old_opts['mirror_pool'] and new_opts['mirror_pool']: new_op = self.add_vdisk_copy(volume.name, dest_pool,
# retype from non mirror vol to mirror vol new_type)
self.add_vdisk_copy(volume['name'], self._add_vdisk_copy_op(ctxt, volume, new_op)
new_opts['mirror_pool'], new_type) else:
elif old_opts['mirror_pool'] and not new_opts['mirror_pool']: self.add_vdisk_copy(volume.name, dest_pool, new_type,
# retype from mirror vol to non mirror vol auto_delete=True)
secondary = copies['secondary'] except exception.VolumeDriverException:
if secondary: # roll back changing iogrp property
self._helpers.rm_vdisk_copy( retype_iogrp_property(volume, old_io_grp, new_io_grp)
volume.name, secondary['copy_id']) msg = (_('Unable to retype: A copy of volume %s exists. '
else: 'Retyping would exceed the limit of 2 copies.'),
# migrate the second copy to another pool. volume['id'])
self._helpers.migratevdisk( raise exception.VolumeDriverException(message=msg)
volume.name, new_opts['mirror_pool'], else:
copies['secondary']['copy_id']) 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']: if new_opts['qos']:
# Add the new QoS setting to the volume. If the volume has an # Add the new QoS setting to the volume. If the volume has an
# old QoS setting, it will be overwritten. # old QoS setting, it will be overwritten.
@ -4222,7 +4585,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
vol_rep_type = None vol_rep_type = None
rel_info = self._helpers.get_relationship_info(vdisk['name']) rel_info = self._helpers.get_relationship_info(vdisk['name'])
copies = self._helpers.get_vdisk_copies(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 = ( vol_rep_type = (
storwize_const.GMCV if storwize_const.GMCV if
storwize_const.GMCV_MULTI == rel_info['cycling_mode'] 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'], opts = self._get_vdisk_params(volume['volume_type_id'],
volume_metadata= volume_metadata=
volume.get('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']) resp = self._helpers.lsvdiskcopy(vdisk['name'])
expected_copy_num = 2 if opts['mirror_pool'] else 1 expected_copy_num = 2 if opts['mirror_pool'] else 1
if len(resp) != expected_copy_num: if len(resp) != expected_copy_num:
@ -4318,7 +4709,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
msg = (_("Failed to manage existing volume due to " msg = (_("Failed to manage existing volume due to "
"I/O group mismatch. The I/O group of the " "I/O group mismatch. The I/O group of the "
"volume to be managed is %(vdisk_iogrp)s. I/O group" "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'], {'vdisk_iogrp': vdisk['IO_group_name'],
'opt_iogrp': opts['iogrp']}) 'opt_iogrp': opts['iogrp']})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg) raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
@ -4393,9 +4784,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return self._stats return self._stats
@staticmethod @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 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 # Add CG capability to generic volume groups
def create_group(self, context, group): def create_group(self, context, group):
@ -4405,7 +4798,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
:param group: the group object. :param group: the group object.
:returns: model_update :returns: model_update
""" """
LOG.debug("Creating group.") LOG.debug("Creating group.")
model_update = {'status': fields.GroupStatus.AVAILABLE} model_update = {'status': fields.GroupStatus.AVAILABLE}
@ -4418,7 +4810,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
support_grps = ['group_snapshot_enabled', support_grps = ['group_snapshot_enabled',
'consistent_group_snapshot_enabled', 'consistent_group_snapshot_enabled',
'consistent_group_replication_enabled'] 'consistent_group_replication_enabled',
'hyperswap_group_enabled']
supported_grp = False supported_grp = False
for grp_spec in support_grps: for grp_spec in support_grps:
if utils.is_group_a_type(group, grp_spec): if utils.is_group_a_type(group, grp_spec):
@ -4442,6 +4835,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'not supported.') 'not supported.')
model_update = {'status': fields.GroupStatus.ERROR} model_update = {'status': fields.GroupStatus.ERROR}
return model_update 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 # We'll rely on the generic group implementation if it is
# a non-consistent snapshot group. # a non-consistent snapshot group.
@ -4489,6 +4890,33 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"Exception: %(exception)s.", "Exception: %(exception)s.",
{'rccg': rccg_name, 'exception': err}) {'rccg': rccg_name, 'exception': err})
model_update = {'status': fields.GroupStatus.ERROR} 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 return model_update
def delete_group(self, context, group, volumes): 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 # we'll rely on the generic group implementation if it is
# not a consistency group and not a consistency replication # 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 if (not utils.is_group_a_cg_snapshot_type(group) and not
utils.is_group_a_type(group, utils.is_group_a_type(group,
"consistent_group_replication_enabled")): "consistent_group_replication_enabled")
and not utils.is_group_a_type(group,
"hyperswap_group_enabled")):
raise NotImplementedError() raise NotImplementedError()
model_update = {'status': fields.GroupStatus.DELETED} model_update = {'status': fields.GroupStatus.DELETED}
@ -4515,6 +4945,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"consistent_group_replication_enabled"): "consistent_group_replication_enabled"):
model_update, volumes_model_update = self._delete_replication_grp( model_update, volumes_model_update = self._delete_replication_grp(
group, volumes) group, volumes)
if utils.is_group_a_type(group, "hyperswap_group_enabled"):
model_update, volumes_model_update = self._delete_hyperswap_grp(
group, volumes)
else: else:
for volume in volumes: for volume in volumes:
try: try:
@ -4547,10 +4982,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.debug("Updating group.") LOG.debug("Updating group.")
# we'll rely on the generic group implementation if it is not a # 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 if (not utils.is_group_a_cg_snapshot_type(group) and not
utils.is_group_a_type(group, utils.is_group_a_type(group,
"consistent_group_replication_enabled")): "consistent_group_replication_enabled")
and not utils.is_group_a_type(group,
"hyperswap_group_enabled")):
raise NotImplementedError() raise NotImplementedError()
if utils.is_group_a_type(group, if utils.is_group_a_type(group,
@ -4558,6 +4996,10 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return self._update_replication_grp(context, group, add_volumes, return self._update_replication_grp(context, group, add_volumes,
remove_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): if utils.is_group_a_cg_snapshot_type(group):
return None, None, None return None, None, None
@ -4585,6 +5027,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.exception(msg) LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=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): if not utils.is_group_a_cg_snapshot_type(group):
# we'll rely on the generic volume groups implementation if it is # we'll rely on the generic volume groups implementation if it is
# not a consistency group request. # not a consistency group request.
@ -4929,3 +5378,82 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"from group. Exception: %(exception)s.", "from group. Exception: %(exception)s.",
{'vol': volume.name, 'exception': err}) {'vol': volume.name, 'exception': err})
return model_update, None, None 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.2 - Add npiv support
2.2.3 - Add replication group support 2.2.3 - Add replication group support
2.2.4 - Add backup snapshots 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 # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -123,10 +124,11 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
# attach the snapshot will be failed. # attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot) 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 = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id, volume = Volume(id=snapshot.id,
name=snapshot.name, name=snapshot.name,
volume_type_id=snapshot.volume_type_id,
display_name='backup-snapshot') display_name='backup-snapshot')
return self.initialize_connection(volume, connector) return self.initialize_connection(volume, connector)
@ -163,12 +165,36 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
else: else:
volume_name, backend_helper, node_state = self._get_vol_sys_info( volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume) 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 # Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector) host_name = backend_helper.get_host_from_connector(connector)
if host_name is None: if host_name is None:
# Host does not exist - add a new host to Storwize/SVC # 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) volume_attributes = backend_helper.get_vdisk_attributes(volume_name)
if volume_attributes is None: 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.1 - Add vdisk mirror/stretch cluster support
2.2.2 - Add replication group support 2.2.2 - Add replication group support
2.2.3 - Add backup snapshots 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 # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -123,10 +124,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# attach the snapshot will be failed. # attach the snapshot will be failed.
self._check_snapshot_replica_volume_status(snapshot) 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 = collections.namedtuple('Volume', vol_attrs)
volume = Volume(id=snapshot.id, volume = Volume(id=snapshot.id,
name=snapshot.name, name=snapshot.name,
volume_type_id=snapshot.volume_type_id,
display_name='backup-snapshot') display_name='backup-snapshot')
return self.initialize_connection(volume, connector) return self.initialize_connection(volume, connector)
@ -161,13 +163,38 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
else: else:
volume_name, backend_helper, node_state = self._get_vol_sys_info( volume_name, backend_helper, node_state = self._get_vol_sys_info(
volume) 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 # Check if a host object is defined for this host name
host_name = backend_helper.get_host_from_connector(connector, host_name = backend_helper.get_host_from_connector(connector,
iscsi=True) iscsi=True)
if host_name is None: if host_name is None:
# Host does not exist - add a new host to Storwize/SVC # 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_secret = backend_helper.get_chap_secret_for_host(host_name)
chap_enabled = self.configuration.storwize_svc_iscsi_chap_enabled chap_enabled = self.configuration.storwize_svc_iscsi_chap_enabled

View File

@ -315,6 +315,9 @@ driver:
- multipath - multipath
- iogrp - iogrp
- mirror_pool - mirror_pool
- volume_topology
- peer_pool
- host_site
These keys have the same semantics as their counterparts in the These keys have the same semantics as their counterparts in the
configuration file. They are set similarly; for example, ``rsize=2`` or 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 - mirror_pool
- volume_topology
- peer_pool
- host_site
.. note:: .. note::
When you change the ``rsize``, ``grainsize`` or ``compression`` 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 If the synchronization is not done manually, Storwize Block Storage
service driver will perform the synchronization and do the failback service driver will perform the synchronization and do the failback
after the synchronization is finished. 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.