From: Vladimir Popovski Date: Fri, 16 Aug 2013 04:34:54 +0000 (+0000) Subject: Add features to Zadara Storage Cinder driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=2f907133aeaee09f58be30e75089c23b13573500;p=openstack-build%2Fcinder-build.git Add features to Zadara Storage Cinder driver - move to Zadara APIs 13.07 - added support for extend volume - added support for create/delete snapshot - added support for create clones from volumes and snaps - added support for multi-backend - added volume stats - added tests Implements: blueprint zadara-cinder-driver-update Change-Id: Iad5908a50980c59df2d8d4702743a0b99f82f9b7 --- diff --git a/cinder/tests/test_zadara.py b/cinder/tests/test_zadara.py index 88c3bb731..5e1c4f687 100644 --- a/cinder/tests/test_zadara.py +++ b/cinder/tests/test_zadara.py @@ -21,13 +21,14 @@ Tests for Zadara VPSA volume driver import copy import httplib +import mox from cinder import exception from cinder.openstack.common import log as logging from cinder import test -from cinder.volume.drivers import zadara - -from lxml import etree +from cinder.volume import configuration as conf +from cinder.volume.drivers.zadara import zadara_opts +from cinder.volume.drivers.zadara import ZadaraVPSAISCSIDriver LOG = logging.getLogger("cinder.volume.driver") @@ -38,7 +39,7 @@ DEFAULT_RUNTIME_VARS = { 'access_key': '0123456789ABCDEF', 'volumes': [], 'servers': [], - 'controllers': [('active_ctrl', {'display_name': 'test_ctrl'})], + 'controllers': [('active_ctrl', {'display-name': 'test_ctrl'})], 'counter': 1000, 'login': """ @@ -99,11 +100,20 @@ class FakeRequest(object): ('/api/volumes.xml', self._create_volume), ('/api/servers.xml', self._create_server), ('/api/servers/*/volumes.xml', self._attach), - ('/api/volumes/*/detach.xml', self._detach)], - 'DELETE': [('/api/volumes/*', self._delete)], + ('/api/volumes/*/detach.xml', self._detach), + ('/api/volumes/*/expand.xml', self._expand), + ('/api/consistency_groups/*/snapshots.xml', + self._create_snapshot), + ('/api/consistency_groups/*/clone.xml', + self._create_clone)], + 'DELETE': [('/api/volumes/*', self._delete), + ('/api/snapshots/*', self._delete_snapshot)], 'GET': [('/api/volumes.xml', self._list_volumes), + ('/api/pools.xml', self._list_pools), ('/api/vcontrollers.xml', self._list_controllers), ('/api/servers.xml', self._list_servers), + ('/api/consistency_groups/*/snapshots.xml', + self._list_vol_snapshots), ('/api/volumes/*/servers.xml', self._list_vol_attachments)] } @@ -156,6 +166,9 @@ class FakeRequest(object): if self._incorrect_access_key(params): return RUNTIME_VARS['bad_login'] + params['display-name'] = params['name'] + params['cg-name'] = params['name'] + params['snapshots'] = [] params['attachments'] = [] vpsa_vol = 'volume-%07d' % self._get_counter() RUNTIME_VARS['volumes'].append((vpsa_vol, params)) @@ -166,6 +179,7 @@ class FakeRequest(object): if self._incorrect_access_key(params): return RUNTIME_VARS['bad_login'] + params['display-name'] = params['display_name'] vpsa_srv = 'srv-%07d' % self._get_counter() RUNTIME_VARS['servers'].append((vpsa_srv, params)) return RUNTIME_VARS['server_created'] % vpsa_srv @@ -209,6 +223,65 @@ class FakeRequest(object): return RUNTIME_VARS['bad_volume'] + def _expand(self): + params = self._get_parameters(self.body) + if self._incorrect_access_key(params): + return RUNTIME_VARS['bad_login'] + + vol = self.url.split('/')[3] + capacity = params['capacity'] + + for (vol_name, params) in RUNTIME_VARS['volumes']: + if vol_name == vol: + params['capacity'] = capacity + return RUNTIME_VARS['good'] + + return RUNTIME_VARS['bad_volume'] + + def _create_snapshot(self): + params = self._get_parameters(self.body) + if self._incorrect_access_key(params): + return RUNTIME_VARS['bad_login'] + + cg_name = self.url.split('/')[3] + snap_name = params['display_name'] + + for (vol_name, params) in RUNTIME_VARS['volumes']: + if params['cg-name'] == cg_name: + snapshots = params['snapshots'] + if snap_name in snapshots: + #already attached + return RUNTIME_VARS['bad_volume'] + else: + snapshots.append(snap_name) + return RUNTIME_VARS['good'] + + return RUNTIME_VARS['bad_volume'] + + def _delete_snapshot(self): + snap = self.url.split('/')[3].split('.')[0] + + for (vol_name, params) in RUNTIME_VARS['volumes']: + if snap in params['snapshots']: + params['snapshots'].remove(snap) + return RUNTIME_VARS['good'] + + return RUNTIME_VARS['bad_volume'] + + def _create_clone(self): + params = self._get_parameters(self.body) + if self._incorrect_access_key(params): + return RUNTIME_VARS['bad_login'] + + params['display-name'] = params['name'] + params['cg-name'] = params['name'] + params['capacity'] = 1 + params['snapshots'] = [] + params['attachments'] = [] + vpsa_vol = 'volume-%07d' % self._get_counter() + RUNTIME_VARS['volumes'].append((vpsa_vol, params)) + return RUNTIME_VARS['good'] + def _delete(self): vol = self.url.split('/')[3].split('.')[0] @@ -223,10 +296,16 @@ class FakeRequest(object): return RUNTIME_VARS['bad_volume'] - def _generate_list_resp(self, header, footer, body, lst): + def _generate_list_resp(self, header, footer, body, lst, vol): resp = header for (obj, params) in lst: - resp += body % (obj, params['display_name']) + if vol: + resp += body % (obj, + params['display-name'], + params['cg-name'], + params['capacity']) + else: + resp += body % (obj, params['display-name']) resp += footer return resp @@ -238,8 +317,9 @@ class FakeRequest(object): body = """ %s %s + %s Available - 1 + %s 1 r5 write-through @@ -249,7 +329,8 @@ class FakeRequest(object): return self._generate_list_resp(header, footer, body, - RUNTIME_VARS['volumes']) + RUNTIME_VARS['volumes'], + True) def _list_controllers(self): header = """ @@ -272,7 +353,16 @@ class FakeRequest(object): return self._generate_list_resp(header, footer, body, - RUNTIME_VARS['controllers']) + RUNTIME_VARS['controllers'], + False) + + def _list_pools(self): + header = """ + 0 + + """ + footer = "" + return header + footer def _list_servers(self): header = """ @@ -290,7 +380,7 @@ class FakeRequest(object): resp = header for (obj, params) in RUNTIME_VARS['servers']: - resp += body % (obj, params['display_name'], params['iqn']) + resp += body % (obj, params['display-name'], params['iqn']) resp += footer return resp @@ -321,13 +411,40 @@ class FakeRequest(object): for server in attachments: srv_params = self._get_server_obj(server) resp += body % (server, - srv_params['display_name'], + srv_params['display-name'], srv_params['iqn']) resp += footer return resp return RUNTIME_VARS['bad_volume'] + def _list_vol_snapshots(self): + cg_name = self.url.split('/')[3] + + header = """ + 0 + """ + footer = "" + + body = """ + %s + %s + normal + %s + pool-00000001 + """ + + for (vol_name, params) in RUNTIME_VARS['volumes']: + if params['cg-name'] == cg_name: + snapshots = params['snapshots'] + resp = header + for snap in snapshots: + resp += body % (snap, snap, cg_name) + resp += footer + return resp + + return RUNTIME_VARS['bad_volume'] + class FakeHTTPConnection(object): """A fake httplib.HTTPConnection for zadara volume driver tests.""" @@ -363,14 +480,18 @@ class ZadaraVPSADriverTestCase(test.TestCase): def setUp(self): LOG.debug('Enter: setUp') super(ZadaraVPSADriverTestCase, self).setUp() - self.flags( - zadara_user='test', - zadara_password='test_password', - ) + global RUNTIME_VARS RUNTIME_VARS = copy.deepcopy(DEFAULT_RUNTIME_VARS) - self.driver = zadara.ZadaraVPSAISCSIDriver() + self.configuration = conf.Configuration(None) + self.configuration.append_config_values(zadara_opts) + self.configuration.reserved_percentage = 10 + self.configuration.zadara_user = 'test' + self.configuration.zadara_password = 'test_password' + self.configuration.zadara_vpsa_poolname = 'pool-0001' + + self.driver = ZadaraVPSAISCSIDriver(configuration=self.configuration) self.stubs.Set(httplib, 'HTTPConnection', FakeHTTPConnection) self.stubs.Set(httplib, 'HTTPSConnection', FakeHTTPSConnection) self.driver.do_setup(None) @@ -417,15 +538,6 @@ class ZadaraVPSADriverTestCase(test.TestCase): self.driver.ensure_export(context, volume) self.driver.remove_export(context, volume) - self.assertRaises(NotImplementedError, - self.driver.create_volume_from_snapshot, - volume, None) - self.assertRaises(NotImplementedError, - self.driver.create_snapshot, - None) - self.assertRaises(NotImplementedError, - self.driver.delete_snapshot, - None) self.assertRaises(NotImplementedError, self.driver.local_path, None) @@ -579,3 +691,107 @@ class ZadaraVPSADriverTestCase(test.TestCase): self.assertRaises(exception.ZadaraVPSANoActiveController, self.driver.initialize_connection, volume, connector) + + def test_create_destroy_snapshot(self): + """Create/Delete snapshot test.""" + volume = {'name': 'test_volume_01', 'size': 1} + snapshot = {'name': 'snap_01', + 'volume_name': volume['name']} + + self.driver.create_volume(volume) + + self.assertRaises(exception.VolumeNotFound, + self.driver.create_snapshot, + {'name': snapshot['name'], + 'volume_name': 'wrong_vol'}) + + self.driver.create_snapshot(snapshot) + + # Deleted should succeed for missing volume + self.driver.delete_snapshot({'name': snapshot['name'], + 'volume_name': 'wrong_vol'}) + # Deleted should succeed for missing snap + self.driver.delete_snapshot({'name': 'wrong_snap', + 'volume_name': volume['name']}) + + self.driver.delete_snapshot(snapshot) + self.driver.delete_volume(volume) + + def test_expand_volume(self): + """Expand volume test.""" + volume = {'name': 'test_volume_01', 'size': 10} + volume2 = {'name': 'test_volume_02', 'size': 10} + + self.driver.create_volume(volume) + + self.assertRaises(exception.VolumeNotFound, + self.driver.extend_volume, + volume2, 15) + self.assertRaises(exception.InvalidInput, + self.driver.extend_volume, + volume, 5) + + self.driver.extend_volume(volume, 15) + self.driver.delete_volume(volume) + + def test_create_destroy_clones(self): + """Create/Delete clones test.""" + volume1 = {'name': 'test_volume_01', 'size': 1} + volume2 = {'name': 'test_volume_02', 'size': 1} + volume3 = {'name': 'test_volume_03', 'size': 1} + snapshot = {'name': 'snap_01', + 'volume_name': volume1['name']} + + self.driver.create_volume(volume1) + self.driver.create_snapshot(snapshot) + + # Test invalid vol reference + self.assertRaises(exception.VolumeNotFound, + self.driver.create_volume_from_snapshot, + volume2, + {'name': snapshot['name'], + 'volume_name': 'wrong_vol'}) + # Test invalid snap reference + self.assertRaises(exception.VolumeNotFound, + self.driver.create_volume_from_snapshot, + volume2, + {'name': 'wrong_snap', + 'volume_name': snapshot['volume_name']}) + # Test invalid src_vref for volume clone + self.assertRaises(exception.VolumeNotFound, + self.driver.create_cloned_volume, + volume3, volume2) + + self.driver.create_volume_from_snapshot(volume2, snapshot) + self.driver.create_cloned_volume(volume3, volume1) + + self.driver.delete_volume(volume3) + self.driver.delete_volume(volume2) + self.driver.delete_snapshot(snapshot) + self.driver.delete_volume(volume1) + + def test_get_volume_stats(self): + """Get stats test.""" + + self.mox.StubOutWithMock(self.configuration, 'safe_get') + self.configuration.safe_get('volume_backend_name'). \ + AndReturn('ZadaraVPSAISCSIDriver') + self.mox.ReplayAll() + + data = self.driver.get_volume_stats(True) + + self.assertEqual(data['vendor_name'], 'Zadara Storage') + self.assertEqual(data['total_capacity_gb'], 'infinite') + self.assertEqual(data['free_capacity_gb'], 'infinite') + + self.assertEquals(data, + {'total_capacity_gb': 'infinite', + 'free_capacity_gb': 'infinite', + 'reserved_percentage': + self.configuration.reserved_percentage, + 'QoS_support': False, + 'vendor_name': 'Zadara Storage', + 'driver_version': self.driver.VERSION, + 'storage_protocol': 'iSCSI', + 'volume_backend_name': 'ZadaraVPSAISCSIDriver', + }) diff --git a/cinder/volume/drivers/zadara.py b/cinder/volume/drivers/zadara.py index 354a439bd..a102e2dec 100644 --- a/cinder/volume/drivers/zadara.py +++ b/cinder/volume/drivers/zadara.py @@ -18,7 +18,7 @@ """ Volume driver for Zadara Virtual Private Storage Array (VPSA). -This driver requires VPSA with API ver.12.06 or higher. +This driver requires VPSA with API ver.13.07 or higher. """ @@ -31,8 +31,7 @@ from cinder import exception from cinder.openstack.common import log as logging from cinder.volume import driver - -LOG = logging.getLogger("cinder.volume.driver") +LOG = logging.getLogger(__name__) zadara_opts = [ cfg.StrOpt('zadara_vpsa_ip', @@ -56,12 +55,12 @@ zadara_opts = [ default=None, help='Name of VPSA storage pool for volumes'), - cfg.StrOpt('zadara_default_cache_policy', - default='write-through', - help='Default cache policy for volumes'), - cfg.StrOpt('zadara_default_encryption', - default='NO', - help='Default encryption policy for volumes'), + cfg.BoolOpt('zadara_vol_thin', + default=True, + help='Default thin provisioning policy for volumes'), + cfg.BoolOpt('zadara_vol_encrypt', + default=False, + help='Default encryption policy for volumes'), cfg.StrOpt('zadara_default_striping_mode', default='simple', help='Default striping mode for volumes'), @@ -85,12 +84,8 @@ CONF.register_opts(zadara_opts) class ZadaraVPSAConnection(object): """Executes volume driver commands on VPSA.""" - def __init__(self, host, port, ssl, user, password): - self.host = host - self.port = port - self.use_ssl = ssl - self.user = user - self.password = password + def __init__(self, conf): + self.conf = conf self.access_key = None self.ensure_connection() @@ -109,25 +104,49 @@ class ZadaraVPSAConnection(object): vpsa_commands = { 'login': ('POST', '/api/users/login.xml', - {'user': self.user, - 'password': self.password}), + {'user': self.conf.zadara_user, + 'password': self.conf.zadara_password}), # Volume operations 'create_volume': ('POST', '/api/volumes.xml', - {'display_name': kwargs.get('name'), - 'virtual_capacity': kwargs.get('size'), - 'raid_group_name[]': CONF.zadara_vpsa_poolname, - 'quantity': 1, - 'cache': CONF.zadara_default_cache_policy, - 'crypt': CONF.zadara_default_encryption, - 'mode': CONF.zadara_default_striping_mode, - 'stripesize': CONF.zadara_default_stripesize, - 'force': 'NO'}), + {'name': kwargs.get('name'), + 'capacity': kwargs.get('size'), + 'pool': self.conf.zadara_vpsa_poolname, + 'thin': 'YES' + if self.conf.zadara_vol_thin else 'NO', + 'crypt': 'YES' + if self.conf.zadara_vol_encrypt else 'NO'}), 'delete_volume': ('DELETE', '/api/volumes/%s.xml' % kwargs.get('vpsa_vol'), {}), + 'expand_volume': ('POST', + '/api/volumes/%s/expand.xml' + % kwargs.get('vpsa_vol'), + {'capacity': kwargs.get('size')}), + + # Snapshot operations + 'create_snapshot': ('POST', + '/api/consistency_groups/%s/snapshots.xml' + % kwargs.get('cg_name'), + {'display_name': kwargs.get('snap_name')}), + 'delete_snapshot': ('DELETE', + '/api/snapshots/%s.xml' + % kwargs.get('snap_id'), + {}), + + 'create_clone_from_snap': ('POST', + '/api/consistency_groups/%s/clone.xml' + % kwargs.get('cg_name'), + {'name': kwargs.get('name'), + 'snapshot': kwargs.get('snap_id')}), + + 'create_clone': ('POST', + '/api/consistency_groups/%s/clone.xml' + % kwargs.get('cg_name'), + {'name': kwargs.get('name')}), + # Server operations 'create_server': ('POST', '/api/servers.xml', @@ -150,6 +169,9 @@ class ZadaraVPSAConnection(object): 'list_volumes': ('GET', '/api/volumes.xml', {}), + 'list_pools': ('GET', + '/api/pools.xml', + {}), 'list_controllers': ('GET', '/api/vcontrollers.xml', {}), @@ -159,7 +181,11 @@ class ZadaraVPSAConnection(object): 'list_vol_attachments': ('GET', '/api/volumes/%s/servers.xml' % kwargs.get('vpsa_vol'), - {}), } + {}), + 'list_vol_snapshots': ('GET', + '/api/consistency_groups/%s/snapshots.xml' + % kwargs.get('cg_name'), + {})} if cmd not in vpsa_commands.keys(): raise exception.UnknownCmd(cmd=cmd) @@ -218,10 +244,12 @@ class ZadaraVPSAConnection(object): LOG.debug(_('Sending %(method)s to %(url)s. Body "%(body)s"'), {'method': method, 'url': url, 'body': body}) - if self.use_ssl: - connection = httplib.HTTPSConnection(self.host, self.port) + if self.conf.zadara_vpsa_use_ssl: + connection = httplib.HTTPSConnection(self.conf.zadara_vpsa_ip, + self.conf.zadara_vpsa_port) else: - connection = httplib.HTTPConnection(self.host, self.port) + connection = httplib.HTTPConnection(self.conf.zadara_vpsa_ip, + self.conf.zadara_vpsa_port) connection.request(method, url, body) response = connection.getresponse() @@ -244,19 +272,18 @@ class ZadaraVPSAConnection(object): class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): """Zadara VPSA iSCSI volume driver.""" + VERSION = '13.07' + def __init__(self, *args, **kwargs): super(ZadaraVPSAISCSIDriver, self).__init__(*args, **kwargs) + self.configuration.append_config_values(zadara_opts) def do_setup(self, context): """ Any initialization the volume driver does while starting. Establishes initial connection with VPSA and retrieves access_key. """ - self.vpsa = ZadaraVPSAConnection(CONF.zadara_vpsa_ip, - CONF.zadara_vpsa_port, - CONF.zadara_vpsa_use_ssl, - CONF.zadara_user, - CONF.zadara_password) + self.vpsa = ZadaraVPSAConnection(self.configuration) def check_for_setup_error(self): """Returns an error (exception) if prerequisites aren't met.""" @@ -291,16 +318,57 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): result_list.append(object) return result_list if result_list else None + def _get_vpsa_volume_name_and_size(self, name): + """Return VPSA's name & size for the volume.""" + xml_tree = self.vpsa.send_cmd('list_volumes') + volume = self._xml_parse_helper(xml_tree, 'volumes', + ('display-name', name)) + if volume is not None: + return (volume.findtext('name'), + int(volume.findtext('virtual-capacity'))) + + return (None, None) + def _get_vpsa_volume_name(self, name): """Return VPSA's name for the volume.""" + (vol_name, size) = self._get_vpsa_volume_name_and_size(name) + return vol_name + + def _get_volume_cg_name(self, name): + """Return name of the consistency group for the volume.""" xml_tree = self.vpsa.send_cmd('list_volumes') volume = self._xml_parse_helper(xml_tree, 'volumes', ('display-name', name)) if volume is not None: - return volume.findtext('name') + return volume.findtext('cg-name') return None + def _get_snap_id(self, cg_name, snap_name): + """Return snapshot ID for particular volume.""" + xml_tree = self.vpsa.send_cmd('list_vol_snapshots', + cg_name=cg_name) + snap = self._xml_parse_helper(xml_tree, 'snapshots', + ('display-name', snap_name)) + if snap is not None: + return snap.findtext('name') + + return None + + def _get_pool_capacity(self, pool_name): + """Return pool's total and available capacities.""" + xml_tree = self.vpsa.send_cmd('list_pools') + pool = self._xml_parse_helper(xml_tree, 'pools', + ('name', pool_name)) + if pool is not None: + total = int(pool.findtext('capacity')) + free = int(float(pool.findtext('available-capacity'))) + LOG.debug(_('Pool %(name)s: %(total)sGB total, %(free)sGB free'), + {'name': pool_name, 'total': total, 'free': free}) + return (total, free) + + return ('infinite', 'infinite') + def _get_active_controller_details(self): """Return details of VPSA's active controller.""" xml_tree = self.vpsa.send_cmd('list_controllers') @@ -334,7 +402,7 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): """Create volume.""" self.vpsa.send_cmd( 'create_volume', - name=CONF.zadara_vol_name_template % volume['name'], + name=self.configuration.zadara_vol_name_template % volume['name'], size=volume['size']) def delete_volume(self, volume): @@ -344,13 +412,13 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): Return ok if doesn't exist. Auto detach from all servers. """ # Get volume name - name = CONF.zadara_vol_name_template % volume['name'] + name = self.configuration.zadara_vol_name_template % volume['name'] vpsa_vol = self._get_vpsa_volume_name(name) if not vpsa_vol: msg = _('Volume %(name)s could not be found. ' 'It might be already deleted') % {'name': name} LOG.warning(msg) - if CONF.zadara_vpsa_allow_nonexistent_delete: + if self.configuration.zadara_vpsa_allow_nonexistent_delete: return else: raise exception.VolumeNotFound(volume_id=name) @@ -361,7 +429,7 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): servers = self._xml_parse_helper(xml_tree, 'servers', ('iqn', None), first=False) if servers: - if not CONF.zadara_vpsa_auto_detach_on_delete: + if not self.configuration.zadara_vpsa_auto_detach_on_delete: raise exception.VolumeAttached(volume_id=name) for server in servers: @@ -374,6 +442,116 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): # Delete volume self.vpsa.send_cmd('delete_volume', vpsa_vol=vpsa_vol) + def create_snapshot(self, snapshot): + """Creates a snapshot.""" + + LOG.debug(_('Create snapshot: %s'), snapshot['name']) + + # Retrieve the CG name for the base volume + volume_name = self.configuration.zadara_vol_name_template\ + % snapshot['volume_name'] + cg_name = self._get_volume_cg_name(volume_name) + if not cg_name: + msg = _('Volume %(name)s not found') % {'name': volume_name} + LOG.error(msg) + raise exception.VolumeNotFound(volume_id=volume_name) + + self.vpsa.send_cmd('create_snapshot', + cg_name=cg_name, + snap_name=snapshot['name']) + + def delete_snapshot(self, snapshot): + """Deletes a snapshot.""" + + LOG.debug(_('Delete snapshot: %s'), snapshot['name']) + + # Retrieve the CG name for the base volume + volume_name = self.configuration.zadara_vol_name_template\ + % snapshot['volume_name'] + cg_name = self._get_volume_cg_name(volume_name) + if not cg_name: + # If the volume isn't present, then don't attempt to delete + LOG.warning(_("snapshot: original volume %s not found, " + "skipping delete operation") + % snapshot['volume_name']) + return True + + snap_id = self._get_snap_id(cg_name, snapshot['name']) + if not snap_id: + # If the snapshot isn't present, then don't attempt to delete + LOG.warning(_("snapshot: snapshot %s not found, " + "skipping delete operation") + % snapshot['name']) + return True + + self.vpsa.send_cmd('delete_snapshot', + snap_id=snap_id) + + def create_volume_from_snapshot(self, volume, snapshot): + """Creates a volume from a snapshot.""" + + LOG.debug(_('Creating volume from snapshot: %s') % snapshot['name']) + + # Retrieve the CG name for the base volume + volume_name = self.configuration.zadara_vol_name_template\ + % snapshot['volume_name'] + cg_name = self._get_volume_cg_name(volume_name) + if not cg_name: + msg = _('Volume %(name)s not found') % {'name': volume_name} + LOG.error(msg) + raise exception.VolumeNotFound(volume_id=volume_name) + + snap_id = self._get_snap_id(cg_name, snapshot['name']) + if not snap_id: + msg = _('Snapshot %(name)s not found') % {'name': snapshot['name']} + LOG.error(msg) + raise exception.VolumeNotFound(volume_id=snapshot['name']) + + self.vpsa.send_cmd('create_clone_from_snap', + cg_name=cg_name, + name=self.configuration.zadara_vol_name_template + % volume['name'], + snap_id=snap_id) + + def create_cloned_volume(self, volume, src_vref): + """Creates a clone of the specified volume.""" + + LOG.debug(_('Creating clone of volume: %s') % src_vref['name']) + + # Retrieve the CG name for the base volume + volume_name = self.configuration.zadara_vol_name_template\ + % src_vref['name'] + cg_name = self._get_volume_cg_name(volume_name) + if not cg_name: + msg = _('Volume %(name)s not found') % {'name': volume_name} + LOG.error(msg) + raise exception.VolumeNotFound(volume_id=volume_name) + + self.vpsa.send_cmd('create_clone', + cg_name=cg_name, + name=self.configuration.zadara_vol_name_template + % volume['name']) + + def extend_volume(self, volume, new_size): + """Extend an existing volume.""" + # Get volume name + name = self.configuration.zadara_vol_name_template % volume['name'] + (vpsa_vol, size) = self._get_vpsa_volume_name_and_size(name) + if not vpsa_vol: + msg = _('Volume %(name)s could not be found. ' + 'It might be already deleted') % {'name': name} + LOG.error(msg) + raise exception.VolumeNotFound(volume_id=name) + + if new_size < size: + raise exception.InvalidInput( + reason='%s < current size %s' % (new_size, size)) + + expand_size = new_size - size + self.vpsa.send_cmd('expand_volume', + vpsa_vol=vpsa_vol, + size=expand_size) + def create_export(self, context, volume): """Irrelevant for VPSA volumes. Export created during attachment.""" pass @@ -404,7 +582,7 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): raise exception.ZadaraServerCreateFailure(name=initiator_name) # Get volume name - name = CONF.zadara_vol_name_template % volume['name'] + name = self.configuration.zadara_vol_name_template % volume['name'] vpsa_vol = self._get_vpsa_volume_name(name) if not vpsa_vol: raise exception.VolumeNotFound(volume_id=name) @@ -460,7 +638,7 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): raise exception.ZadaraServerNotFound(name=initiator_name) # Get volume name - name = CONF.zadara_vol_name_template % volume['name'] + name = self.configuration.zadara_vol_name_template % volume['name'] vpsa_vol = self._get_vpsa_volume_name(name) if not vpsa_vol: raise exception.VolumeNotFound(volume_id=name) @@ -469,3 +647,33 @@ class ZadaraVPSAISCSIDriver(driver.ISCSIDriver): self.vpsa.send_cmd('detach_volume', vpsa_srv=vpsa_srv, vpsa_vol=vpsa_vol) + + def get_volume_stats(self, refresh=False): + """Get volume stats. + If 'refresh' is True, run update the stats first. + """ + if refresh: + self._update_volume_stats() + + return self._stats + + def _update_volume_stats(self): + """Retrieve stats info from volume group.""" + + LOG.debug(_("Updating volume stats")) + data = {} + + backend_name = self.configuration.safe_get('volume_backend_name') + data["volume_backend_name"] = backend_name or self.__class__.__name__ + data["vendor_name"] = 'Zadara Storage' + data["driver_version"] = self.VERSION + data["storage_protocol"] = 'iSCSI' + data['reserved_percentage'] = self.configuration.reserved_percentage + data['QoS_support'] = False + + (total, free) = self._get_pool_capacity(self.configuration. + zadara_vpsa_poolname) + data['total_capacity_gb'] = total + data['free_capacity_gb'] = free + + self._stats = data diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 3d71f0e2d..2f1069714 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -1647,11 +1647,11 @@ # Name of VPSA storage pool for volumes (string value) #zadara_vpsa_poolname= -# Default cache policy for volumes (string value) -#zadara_default_cache_policy=write-through +# Default thin provisioning policy for volumes (boolean value) +#zadara_vol_thin=true -# Default encryption policy for volumes (string value) -#zadara_default_encryption=NO +# Default encryption policy for volumes (boolean value) +#zadara_vol_encrypt=false # Default striping mode for volumes (string value) #zadara_default_striping_mode=simple