-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2013 IBM Corp.
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
self._hosts_list = {}
self._mappings_list = {}
self._fcmappings_list = {}
+ self._other_pools = {'openstack2': {}, 'openstack3': {}}
self._next_cmd_error = {
'lsportip': '',
'lsfabric': '',
'CMMVC7050E': ('', 'CMMVC7050E The command failed because at '
'least one node in the I/O group does not '
'support compressed VDisks.'),
+ 'CMMVC6430E': ('', 'CMMVC6430E The command failed because the '
+ 'target and source managed disk groups must '
+ 'be different.'),
+ 'CMMVC6353E': ('', 'CMMVC6353E The command failed because the '
+ 'copy specified does not exist.'),
+ 'CMMVC6446E': ('', 'The command failed because the managed disk '
+ 'groups have different extent sizes.'),
# Catch-all for invalid state transitions:
'CMMVC5903E': ('', 'CMMVC5903E The FlashCopy mapping was not '
'changed because the mapping or consistency '
one_param_args = [
'chapsecret',
'cleanrate',
+ 'copy',
'copyrate',
'delim',
+ 'easytier',
'filtervalue',
'grainsize',
'hbawwpn',
'source',
'target',
'unit',
- 'easytier',
+ 'vdisk',
'warning',
'wwpn',
]
# Print mostly made-up stuff in the correct syntax, assume -bytes passed
def _cmd_lsmdiskgrp(self, **kwargs):
- rows = [None] * 3
+ rows = [None] * 4
rows[0] = ['id', 'name', 'status', 'mdisk_count',
'vdisk_count', 'capacity', 'extent_size',
'free_capacity', 'virtual_capacity', 'used_capacity',
'1', str(len(self._volumes_list)), '3573412790272',
'256', '3529926246400', '1693247906775', '277841182',
'38203734097', '47', '80', 'auto', 'inactive']
- rows[2] = ['2', 'volpool2', 'online',
+ rows[2] = ['2', 'openstack2', 'online',
'1', '0', '3573412790272', '256',
'3529432325160', '1693247906775', '277841182',
'38203734097', '47', '80', 'auto', 'inactive']
+ rows[3] = ['3', 'openstack3', 'online',
+ '1', '0', '3573412790272', '128',
+ '3529432325160', '1693247906775', '277841182',
+ '38203734097', '47', '80', 'auto', 'inactive']
if 'obj' not in kwargs:
return self._print_info_cmd(rows=rows, **kwargs)
else:
if kwargs['obj'] == self._flags['storwize_svc_volpool_name']:
row = rows[1]
- elif kwargs['obj'] == 'volpool2':
+ elif kwargs['obj'] == 'openstack2':
row = rows[2]
+ elif kwargs['obj'] == 'openstack3':
+ row = rows[3]
else:
return self._errors['CMMVC5754E']
volume_info['grainsize'] = ''
volume_info['compressed_copy'] = 'no'
+ vol_cp = {'id': '0',
+ 'status': 'online',
+ 'sync': 'yes',
+ 'primary': 'yes',
+ 'mdisk_grp_id': '1',
+ 'mdisk_grp_name': self._flags['storwize_svc_volpool_name'],
+ 'easy_tier': volume_info['easy_tier'],
+ 'compressed_copy': volume_info['compressed_copy']}
+ volume_info['copies'] = {'0': vol_cp}
+
if volume_info['name'] in self._volumes_list:
return self._errors['CMMVC6035E']
else:
return self._print_info_cmd(rows=rows, **kwargs)
- # Add host to list
+ def _cmd_migratevdisk(self, **kwargs):
+ if 'mdiskgrp' not in kwargs or 'vdisk' not in kwargs:
+ return self._errors['CMMVC5707E']
+ mdiskgrp = kwargs['mdiskgrp'].strip('\'\'')
+ vdisk = kwargs['vdisk'].strip('\'\'')
+
+ if vdisk in self._volumes_list:
+ curr_mdiskgrp = self._volumes_list
+ else:
+ for pool in self._other_pools:
+ if vdisk in pool:
+ curr_mdiskgrp = pool
+ break
+ else:
+ return self._errors['CMMVC5754E']
+
+ if mdiskgrp == self._flags['storwize_svc_volpool_name']:
+ tgt_mdiskgrp = self._volumes_list
+ elif mdiskgrp == 'openstack2':
+ tgt_mdiskgrp = self._other_pools['openstack2']
+ elif mdiskgrp == 'openstack3':
+ tgt_mdiskgrp = self._other_pools['openstack3']
+ else:
+ return self._errors['CMMVC5754E']
+
+ if curr_mdiskgrp == tgt_mdiskgrp:
+ return self._errors['CMMVC6430E']
+
+ vol = curr_mdiskgrp[vdisk]
+ tgt_mdiskgrp[vdisk] = vol
+ del curr_mdiskgrp[vdisk]
+ return ('', '')
+
+ def _cmd_addvdiskcopy(self, **kwargs):
+ if 'obj' not in kwargs:
+ return self._errors['CMMVC5701E']
+ vol_name = kwargs['obj'].strip('\'\'')
+ if vol_name not in self._volumes_list:
+ return self._errors['CMMVC5753E']
+ vol = self._volumes_list[vol_name]
+ if 'mdiskgrp' not in kwargs:
+ return self._errors['CMMVC5707E']
+ mdiskgrp = kwargs['mdiskgrp'].strip('\'\'')
+
+ copy_info = {}
+ copy_info['id'] = self._find_unused_id(vol['copies'])
+ copy_info['status'] = 'online'
+ copy_info['sync'] = 'no'
+ copy_info['primary'] = 'no'
+ copy_info['mdisk_grp_name'] = mdiskgrp
+ if mdiskgrp == self._flags['storwize_svc_volpool_name']:
+ copy_info['mdisk_grp_id'] = '1'
+ elif mdiskgrp == 'openstack2':
+ copy_info['mdisk_grp_id'] = '2'
+ elif mdiskgrp == 'openstack3':
+ copy_info['mdisk_grp_id'] = '3'
+ if 'easytier' in kwargs:
+ if kwargs['easytier'] == 'on':
+ copy_info['easy_tier'] = 'on'
+ else:
+ copy_info['easy_tier'] = 'off'
+ if 'rsize' in kwargs:
+ if 'compressed' in kwargs:
+ copy_info['compressed_copy'] = 'yes'
+ else:
+ copy_info['compressed_copy'] = 'no'
+ vol['copies'][copy_info['id']] = copy_info
+ return ('Vdisk [%(vid)s] copy [%(cid)s] successfully created' %
+ {'vid': vol['id'], 'cid': copy_info['id']}, '')
+
+ def _cmd_lsvdiskcopy(self, **kwargs):
+ if 'obj' not in kwargs:
+ return self._errors['CMMVC5804E']
+ name = kwargs['obj']
+ vol = self._volumes_list[name]
+ rows = []
+ rows.append(['vdisk_id', 'vdisk_name', 'copy_id', 'status', 'sync',
+ 'primary', 'mdisk_grp_id', 'mdisk_grp_name', 'capacity',
+ 'type', 'se_copy', 'easy_tier', 'easy_tier_status',
+ 'compressed_copy'])
+ for k, copy in vol['copies'].iteritems():
+ rows.append([vol['id'], vol['name'], copy['id'],
+ copy['status'], copy['sync'], copy['primary'],
+ copy['mdisk_grp_id'], copy['mdisk_grp_name'],
+ vol['capacity'], 'striped', 'yes', copy['easy_tier'],
+ 'inactive', copy['compressed_copy']])
+ if 'copy' not in kwargs:
+ return self._print_info_cmd(rows=rows, **kwargs)
+ else:
+ copy_id = kwargs['copy'].strip('\'\'')
+ if copy_id not in vol['copies']:
+ return self._errors['CMMVC6353E']
+ copy = vol['copies'][copy_id]
+ rows = []
+ rows.append(['vdisk_id', vol['id']])
+ rows.append(['vdisk_name', vol['name']])
+ rows.append(['capacity', vol['capacity']])
+ rows.append(['copy_id', copy['id']])
+ rows.append(['status', copy['status']])
+ rows.append(['sync', copy['sync']])
+ copy['sync'] = 'yes'
+ rows.append(['primary', copy['primary']])
+ rows.append(['mdisk_grp_id', copy['mdisk_grp_id']])
+ rows.append(['mdisk_grp_name', copy['mdisk_grp_name']])
+ rows.append(['easy_tier', copy['easy_tier']])
+ rows.append(['easy_tier_status', 'inactive'])
+ rows.append(['compressed_copy', copy['compressed_copy']])
+
+ if 'delim' in kwargs:
+ for index in range(len(rows)):
+ rows[index] = kwargs['delim'].join(rows[index])
+
+ return ('%s' % '\n'.join(rows), '')
+
+ def _cmd_rmvdiskcopy(self, **kwargs):
+ if 'obj' not in kwargs:
+ return self._errors['CMMVC5701E']
+ vol_name = kwargs['obj'].strip('\'\'')
+ if 'copy' not in kwargs:
+ return self._errors['CMMVC5707E']
+ copy_id = kwargs['copy'].strip('\'\'')
+ if vol_name not in self._volumes_list:
+ return self._errors['CMMVC5753E']
+ vol = self._volumes_list[vol_name]
+ if copy_id not in vol['copies']:
+ return self._errors['CMMVC6353E']
+ del vol['copies'][copy_id]
+ return ('', '')
+
def _add_host_to_list(self, connector):
host_info = {}
host_info['id'] = self._find_unused_id(self._hosts_list)
out, err = self._cmd_lsfcmap(**kwargs)
elif command == 'lsvdiskfcmappings':
out, err = self._cmd_lsvdiskfcmappings(**kwargs)
+ elif command == 'migratevdisk':
+ out, err = self._cmd_migratevdisk(**kwargs)
+ elif command == 'addvdiskcopy':
+ out, err = self._cmd_addvdiskcopy(**kwargs)
+ elif command == 'lsvdiskcopy':
+ out, err = self._cmd_lsvdiskcopy(**kwargs)
+ elif command == 'rmvdiskcopy':
+ out, err = self._cmd_rmvdiskcopy(**kwargs)
else:
out, err = ('', 'ERROR: Unsupported command')
self._def_flags = {'san_ip': 'hostname',
'san_login': 'user',
'san_password': 'pass',
+ 'storwize_svc_volpool_name': 'openstack',
'storwize_svc_flashcopy_timeout': 20,
# Test ignore capitalization
'storwize_svc_connection_protocol': 'iScSi',
'host': 'storwize-svc-test',
'wwpns': wwpns,
'initiator': initiator}
- self.sim = StorwizeSVCManagementSimulator('volpool')
+ self.sim = StorwizeSVCManagementSimulator('openstack')
self.driver.set_fake_storage(self.sim)
else:
self.assertLessEqual(stats['free_capacity_gb'],
stats['total_capacity_gb'])
self.assertEquals(stats['reserved_percentage'], 25)
+ pool = self.driver.configuration.local_conf.storwize_svc_volpool_name
if self.USESIM:
- self.assertEqual(stats['volume_backend_name'],
- 'storwize-svc-sim_volpool')
+ expected = 'storwize-svc-sim_' + pool
+ self.assertEqual(stats['volume_backend_name'], expected)
self.assertAlmostEqual(stats['total_capacity_gb'], 3328.0)
self.assertAlmostEqual(stats['free_capacity_gb'], 3287.5)
self.driver.delete_snapshot(snap)
self.driver.delete_volume(volume)
+ def _check_loc_info(self, capabilities, expected):
+ host = {'host': 'foo', 'capabilities': capabilities}
+ vol = {'name': 'test', 'id': 1, 'size': 1}
+ ctxt = context.get_admin_context()
+ moved, model_update = self.driver.migrate_volume(ctxt, vol, host)
+ self.assertEqual(moved, expected['moved'])
+ self.assertEqual(model_update, expected['model_update'])
+
+ def test_storwize_svc_migrate_bad_loc_info(self):
+ self._check_loc_info({}, {'moved': False, 'model_update': None})
+ cap = {'location_info': 'foo'}
+ self._check_loc_info(cap, {'moved': False, 'model_update': None})
+ cap = {'location_info': 'FooDriver:foo:bar'}
+ self._check_loc_info(cap, {'moved': False, 'model_update': None})
+ cap = {'location_info': 'StorwizeSVCDriver:foo:bar'}
+ self._check_loc_info(cap, {'moved': False, 'model_update': None})
+
+ def test_storwize_svc_migrate_same_extent_size(self):
+ def _copy_info_exc(self, name):
+ raise Exception('should not be called')
+
+ self.stubs.Set(self.driver, '_get_vdisk_copy_info', _copy_info_exc)
+ self.driver.do_setup(None)
+ loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack2'
+ cap = {'location_info': loc, 'extent_size': '256'}
+ host = {'host': 'foo', 'capabilities': cap}
+ ctxt = context.get_admin_context()
+ volume = self._generate_vol_info(None, None)
+ volume['volume_type_id'] = None
+ self.driver.create_volume(volume)
+ self.driver.migrate_volume(ctxt, volume, host)
+ self.driver.delete_volume(volume)
+
+ def test_storwize_svc_migrate_diff_extent_size(self):
+ self.driver.do_setup(None)
+ loc = 'StorwizeSVCDriver:' + self.driver._system_id + ':openstack3'
+ cap = {'location_info': loc, 'extent_size': '128'}
+ host = {'host': 'foo', 'capabilities': cap}
+ ctxt = context.get_admin_context()
+ volume = self._generate_vol_info(None, None)
+ volume['volume_type_id'] = None
+ self.driver.create_volume(volume)
+ self.assertNotEquals(cap['extent_size'], self.driver._extent_size)
+ self.driver.migrate_volume(ctxt, volume, host)
+ self.driver.delete_volume(volume)
+
class CLIResponseTestCase(test.TestCase):
def test_empty(self):
self._compression_enabled = False
self._available_iogrps = []
self._context = None
+ self._system_name = None
+ self._system_id = None
+ self._extent_size = None
# Build cleanup translation tables for host names
invalid_ch_in_host = ''
LOG.debug(_('enter: do_setup'))
self._context = ctxt
+ # Get storage system name and id
+ ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
+ attributes = self._execute_command_and_parse_attributes(ssh_cmd)
+ if not attributes or not attributes['name']:
+ msg = (_('do_setup: Could not get system name'))
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+ self._system_name = attributes['name']
+ self._system_id = attributes['id']
+
# Validate that the pool exists
- ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-delim', '!', '-nohdr']
- out, err = self._run_ssh(ssh_cmd)
- self._assert_ssh_return(len(out.strip()), 'do_setup',
- ssh_cmd, out, err)
- search_text = '!%s!' % self.configuration.storwize_svc_volpool_name
- if search_text not in out:
- raise exception.InvalidInput(
- reason=(_('pool %s doesn\'t exist')
- % self.configuration.storwize_svc_volpool_name))
+ pool = self.configuration.storwize_svc_volpool_name
+ ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
+ attributes = self._execute_command_and_parse_attributes(ssh_cmd)
+ if not attributes:
+ msg = (_('do_setup: Pool %s does not exist') % pool)
+ LOG.error(msg)
+ raise exception.InvalidInput(reason=msg)
+ self._extent_size = attributes['extent_size']
# Check if compression is supported
self._compression_enabled = False
"""Ensure that the flags are set properly."""
LOG.debug(_('enter: check_for_setup_error'))
+ # Check that we have the system ID information
+ if self._system_name is None:
+ exception_msg = (_('Unable to determine system name'))
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+ if self._system_id is None:
+ exception_msg = (_('Unable to determine system id'))
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+ if self._extent_size is None:
+ exception_msg = (_('Unable to determine pool extent size'))
+ raise exception.VolumeBackendAPIException(data=exception_msg)
+
required_flags = ['san_ip', 'san_ssh_port', 'san_login',
'storwize_svc_volpool_name']
for flag in required_flags:
LOG.debug(_('enter: _create_vdisk: vdisk %s ') % name)
model_update = None
- easytier = 'on' if opts['easytier'] else 'off'
-
- # Set space-efficient options
- if opts['rsize'] == -1:
- ssh_cmd_se_opt = []
- else:
- ssh_cmd_se_opt = ['-rsize', '%s%%' % str(opts['rsize']),
- '-autoexpand', '-warning',
- '%s%%' % str(opts['warning'])]
- if not opts['autoexpand']:
- ssh_cmd_se_opt.remove('-autoexpand')
-
- if opts['compression']:
- ssh_cmd_se_opt.append('-compressed')
- else:
- ssh_cmd_se_opt.extend(['-grainsize', str(opts['grainsize'])])
+ params = self._get_vdisk_create_params(opts)
ssh_cmd = ['svctask', 'mkvdisk', '-name', name, '-mdiskgrp',
self.configuration.storwize_svc_volpool_name,
'-iogrp', str(opts['iogrp']), '-size', size, '-unit',
- units, '-easytier', easytier] + ssh_cmd_se_opt
+ units] + params
out, err = self._run_ssh(ssh_cmd)
self._assert_ssh_return(len(out.strip()), '_create_vdisk',
ssh_cmd, out, err)
ssh_cmd, out, err)
LOG.debug(_('leave: extend_volume: volume %s') % volume['id'])
+ def migrate_volume(self, ctxt, volume, host):
+ """Migrate direclty if source and dest are managed by same storage.
+
+ The method uses the migratevdisk method, which returns almost
+ immediately, if the source and target pools have the same extent_size.
+ Otherwise, it uses addvdiskcopy and rmvdiskcopy, which require waiting
+ for the copy operation to complete.
+
+ :param ctxt: Context
+ :param volume: A dictionary describing the volume to migrate
+ :param host: A dictionary describing the host to migrate to, where
+ host['host'] is its name, and host['capabilities'] is a
+ dictionary of its reported capabilities.
+ """
+ LOG.debug(_('enter: migrate_volume: id=%(id)s, host=%(host)s') %
+ {'id': volume['id'], 'host': host['host']})
+
+ false_ret = (False, None)
+ if 'location_info' not in host['capabilities']:
+ return false_ret
+ info = host['capabilities']['location_info']
+ try:
+ (dest_type, dest_id, dest_pool) = info.split(':')
+ except ValueError:
+ return false_ret
+ if (dest_type != 'StorwizeSVCDriver' or dest_id != self._system_id):
+ return false_ret
+
+ if 'extent_size' not in host['capabilities']:
+ return false_ret
+ if host['capabilities']['extent_size'] == self._extent_size:
+ # If source and dest pools have the same extent size, migratevdisk
+ ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool,
+ '-vdisk', volume['name']]
+ out, err = self._run_ssh(ssh_cmd)
+ # No output should be returned from migratevdisk
+ self._assert_ssh_return(len(out.strip()) == 0, 'migrate_volume',
+ ssh_cmd, out, err)
+ else:
+ # If source and dest pool extent size differ, add/delete vdisk copy
+ copy_info = self._get_vdisk_copy_info(volume['name'])
+ copies = list(copy_info.keys())
+ self._driver_assert(len(copies) == 1,
+ _('migrate_volume started with more than one '
+ 'vdisk copy'))
+ orig_copy_id = copies[0]
+
+ opts = self._get_vdisk_params(volume['volume_type_id'])
+ params = self._get_vdisk_create_params(opts)
+ ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp',
+ dest_pool, volume['name']])
+ out, err = self._run_ssh(ssh_cmd)
+ self._assert_ssh_return(len(out.strip()), 'migrate_volume',
+ ssh_cmd, out, err)
+
+ # Ensure that the output is as expected
+ match_obj = re.search('Vdisk \[([0-9]+)\] copy \[([0-9]+)\] '
+ 'successfully created', out)
+ # Make sure we got a "successfully created" message with copy id
+ self._driver_assert(
+ match_obj is not None,
+ _('migrate_volume %(name)s - did not find '
+ 'success message in CLI output.\n '
+ 'stdout: %(out)s\n stderr: %(err)s')
+ % {'name': volume['name'], 'out': str(out), 'err': str(err)})
+
+ copy_id = match_obj.group(2)
+ sync = False
+ while not sync:
+ ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!', '-copy',
+ copy_id, volume['name']]
+ attrs = self._execute_command_and_parse_attributes(ssh_cmd)
+ if not attrs:
+ msg = (_('migrate_volume: Could not get vdisk copy data'))
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+ if attrs['sync'] == 'yes':
+ sync = True
+ else:
+ time.sleep(5)
+
+ ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', orig_copy_id,
+ volume['name']]
+ out, err = self._run_ssh(ssh_cmd)
+ # No output should be returned from rmvdiskcopy
+ self._assert_ssh_return(len(out.strip()) == 0, 'migrate_volume',
+ ssh_cmd, out, err)
+
+ LOG.debug(_('leave: migrate_volume: id=%(id)s, host=%(host)s') %
+ {'id': volume['id'], 'host': host['host']})
+ return (True, None)
+
"""====================================================================="""
""" MISC/HELPERS """
"""====================================================================="""
data['QoS_support'] = False
pool = self.configuration.storwize_svc_volpool_name
- #Get storage system name
- ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!']
- attributes = self._execute_command_and_parse_attributes(ssh_cmd)
- if not attributes or not attributes['name']:
- exception_message = (_('_update_volume_stats: '
- 'Could not get system name.'))
- LOG.error(exception_message)
- raise exception.VolumeBackendAPIException(data=exception_message)
-
backend_name = self.configuration.safe_get('volume_backend_name')
if not backend_name:
- backend_name = '%s_%s' % (attributes['name'], pool)
+ backend_name = '%s_%s' % (self._system_name, pool)
data['volume_backend_name'] = backend_name
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool]
(1024 ** 3))
data['easytier_support'] = attributes['easy_tier'] in ['on', 'auto']
data['compression_support'] = self._compression_enabled
+ data['extent_size'] = self._extent_size
+ data['location_info'] = ('StorwizeSVCDriver:%(sys_id)s:%(pool)s' %
+ {'sys_id': self._system_id,
+ 'pool': pool})
self._stats = data
ssh_cmd, out, err)
yield port_data
+ def _get_vdisk_copy_info(self, vdisk):
+ ssh_cmd = ['svcinfo', 'lsvdiskcopy', '-delim', '!', vdisk]
+ out, err = self._run_ssh(ssh_cmd)
+
+ self._assert_ssh_return(len(out.strip()), '_get_vdisk_copy_info',
+ ssh_cmd, out, err)
+ copy_lines = out.strip().split('\n')
+ self._assert_ssh_return(len(copy_lines), '_get_vdisk_copy_info',
+ ssh_cmd, out, err)
+
+ header = copy_lines.pop(0)
+ ret = {}
+ for copy_line in copy_lines:
+ try:
+ copy_data = self._get_hdr_dic(header, copy_line, '!')
+ except exception.VolumeBackendAPIException:
+ with excutils.save_and_reraise_exception():
+ self._log_cli_output_error('_get_vdisk_copy_info',
+ ssh_cmd, out, err)
+ ret[copy_data['copy_id']] = copy_data
+ return ret
+
+ def _get_vdisk_create_params(self, opts):
+ easytier = 'on' if opts['easytier'] else 'off'
+
+ # Set space-efficient options
+ if opts['rsize'] == -1:
+ params = []
+ else:
+ params = ['-rsize', '%s%%' % str(opts['rsize']),
+ '-autoexpand', '-warning',
+ '%s%%' % str(opts['warning'])]
+ if not opts['autoexpand']:
+ params.remove('-autoexpand')
+
+ if opts['compression']:
+ params.append('-compressed')
+ else:
+ params.extend(['-grainsize', str(opts['grainsize'])])
+
+ params.extend(['-easytier', easytier])
+ return params
+
def _check_vdisk_opts(self, opts):
# Check that rsize is either -1 or between 0 and 100
if not (opts['rsize'] >= -1 and opts['rsize'] <= 100):