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")
'access_key': '0123456789ABCDEF',
'volumes': [],
'servers': [],
- 'controllers': [('active_ctrl', {'display_name': 'test_ctrl'})],
+ 'controllers': [('active_ctrl', {'display-name': 'test_ctrl'})],
'counter': 1000,
'login': """
('/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)]
}
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))
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
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]
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
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>
return self._generate_list_resp(header,
footer,
body,
- RUNTIME_VARS['volumes'])
+ RUNTIME_VARS['volumes'],
+ True)
def _list_controllers(self):
header = """<show-vcontrollers-response>
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>
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
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."""
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)
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)
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',
+ })
"""
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.
"""
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',
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'),
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()
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',
'list_volumes': ('GET',
'/api/volumes.xml',
{}),
+ 'list_pools': ('GET',
+ '/api/pools.xml',
+ {}),
'list_controllers': ('GET',
'/api/vcontrollers.xml',
{}),
'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)
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()
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."""
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')
"""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):
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)
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:
# 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
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)
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)
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