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
This commit is contained in:
Vladimir Popovski
2013-08-16 04:34:54 +00:00
parent 1428e6f62c
commit 2f907133ae
3 changed files with 499 additions and 75 deletions

View File

@@ -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 = """<volume>
<name>%s</name>
<display-name>%s</display-name>
<cg-name>%s</cg-name>
<status>Available</status>
<virtual-capacity type='integer'>1</virtual-capacity>
<virtual-capacity type='integer'>%s</virtual-capacity>
<allocated-capacity type='integer'>1</allocated-capacity>
<raid-group-name>r5</raid-group-name>
<cache>write-through</cache>
@@ -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 = """<show-vcontrollers-response>
@@ -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 = """<show-pools-response>
<status type="integer">0</status>
<pools type="array">
"""
footer = "</pools></show-pools-response>"
return header + footer
def _list_servers(self):
header = """<show-servers-response>
@@ -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 = """<show-snapshots-on-cg-response>
<status type="integer">0</status>
<snapshots type="array">"""
footer = "</snapshots></show-snapshots-on-cg-response>"
body = """<snapshot>
<name>%s</name>
<display-name>%s</display-name>
<status>normal</status>
<cg-name>%s</cg-name>
<pool-name>pool-00000001</pool-name>
</snapshot>"""
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',
})

View File

@@ -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(self, name):
"""Return VPSA's name for the volume."""
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')
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('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

View File

@@ -1647,11 +1647,11 @@
# Name of VPSA storage pool for volumes (string value)
#zadara_vpsa_poolname=<None>
# 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