57
56
help='Name of VPSA storage pool for volumes'),
59
cfg.StrOpt('zadara_default_cache_policy',
60
default='write-through',
61
help='Default cache policy for volumes'),
62
cfg.StrOpt('zadara_default_encryption',
64
help='Default encryption policy for volumes'),
58
cfg.BoolOpt('zadara_vol_thin',
60
help='Default thin provisioning policy for volumes'),
61
cfg.BoolOpt('zadara_vol_encrypt',
63
help='Default encryption policy for volumes'),
65
64
cfg.StrOpt('zadara_default_striping_mode',
67
66
help='Default striping mode for volumes'),
109
104
vpsa_commands = {
110
105
'login': ('POST',
111
106
'/api/users/login.xml',
113
'password': self.password}),
107
{'user': self.conf.zadara_user,
108
'password': self.conf.zadara_password}),
115
110
# Volume operations
116
111
'create_volume': ('POST',
117
112
'/api/volumes.xml',
118
{'display_name': kwargs.get('name'),
119
'virtual_capacity': kwargs.get('size'),
120
'raid_group_name[]': CONF.zadara_vpsa_poolname,
122
'cache': CONF.zadara_default_cache_policy,
123
'crypt': CONF.zadara_default_encryption,
124
'mode': CONF.zadara_default_striping_mode,
125
'stripesize': CONF.zadara_default_stripesize,
113
{'name': kwargs.get('name'),
114
'capacity': kwargs.get('size'),
115
'pool': self.conf.zadara_vpsa_poolname,
117
if self.conf.zadara_vol_thin else 'NO',
119
if self.conf.zadara_vol_encrypt else 'NO'}),
127
120
'delete_volume': ('DELETE',
128
121
'/api/volumes/%s.xml' % kwargs.get('vpsa_vol'),
124
'expand_volume': ('POST',
125
'/api/volumes/%s/expand.xml'
126
% kwargs.get('vpsa_vol'),
127
{'capacity': kwargs.get('size')}),
129
# Snapshot operations
130
'create_snapshot': ('POST',
131
'/api/consistency_groups/%s/snapshots.xml'
132
% kwargs.get('cg_name'),
133
{'display_name': kwargs.get('snap_name')}),
134
'delete_snapshot': ('DELETE',
135
'/api/snapshots/%s.xml'
136
% kwargs.get('snap_id'),
139
'create_clone_from_snap': ('POST',
140
'/api/consistency_groups/%s/clone.xml'
141
% kwargs.get('cg_name'),
142
{'name': kwargs.get('name'),
143
'snapshot': kwargs.get('snap_id')}),
145
'create_clone': ('POST',
146
'/api/consistency_groups/%s/clone.xml'
147
% kwargs.get('cg_name'),
148
{'name': kwargs.get('name')}),
131
150
# Server operations
132
151
'create_server': ('POST',
133
152
'/api/servers.xml',
218
244
LOG.debug(_('Sending %(method)s to %(url)s. Body "%(body)s"'),
219
245
{'method': method, 'url': url, 'body': body})
222
connection = httplib.HTTPSConnection(self.host, self.port)
247
if self.conf.zadara_vpsa_use_ssl:
248
connection = httplib.HTTPSConnection(self.conf.zadara_vpsa_ip,
249
self.conf.zadara_vpsa_port)
224
connection = httplib.HTTPConnection(self.host, self.port)
251
connection = httplib.HTTPConnection(self.conf.zadara_vpsa_ip,
252
self.conf.zadara_vpsa_port)
225
253
connection.request(method, url, body)
226
254
response = connection.getresponse()
244
272
class ZadaraVPSAISCSIDriver(driver.ISCSIDriver):
245
273
"""Zadara VPSA iSCSI volume driver."""
247
277
def __init__(self, *args, **kwargs):
248
278
super(ZadaraVPSAISCSIDriver, self).__init__(*args, **kwargs)
279
self.configuration.append_config_values(zadara_opts)
250
281
def do_setup(self, context):
252
283
Any initialization the volume driver does while starting.
253
284
Establishes initial connection with VPSA and retrieves access_key.
255
self.vpsa = ZadaraVPSAConnection(CONF.zadara_vpsa_ip,
256
CONF.zadara_vpsa_port,
257
CONF.zadara_vpsa_use_ssl,
259
CONF.zadara_password)
286
self.vpsa = ZadaraVPSAConnection(self.configuration)
261
288
def check_for_setup_error(self):
262
289
"""Returns an error (exception) if prerequisites aren't met."""
291
318
result_list.append(object)
292
319
return result_list if result_list else None
321
def _get_vpsa_volume_name_and_size(self, name):
322
"""Return VPSA's name & size for the volume."""
323
xml_tree = self.vpsa.send_cmd('list_volumes')
324
volume = self._xml_parse_helper(xml_tree, 'volumes',
325
('display-name', name))
326
if volume is not None:
327
return (volume.findtext('name'),
328
int(volume.findtext('virtual-capacity')))
294
332
def _get_vpsa_volume_name(self, name):
295
333
"""Return VPSA's name for the volume."""
334
(vol_name, size) = self._get_vpsa_volume_name_and_size(name)
337
def _get_volume_cg_name(self, name):
338
"""Return name of the consistency group for the volume."""
296
339
xml_tree = self.vpsa.send_cmd('list_volumes')
297
340
volume = self._xml_parse_helper(xml_tree, 'volumes',
298
341
('display-name', name))
299
342
if volume is not None:
300
return volume.findtext('name')
343
return volume.findtext('cg-name')
347
def _get_snap_id(self, cg_name, snap_name):
348
"""Return snapshot ID for particular volume."""
349
xml_tree = self.vpsa.send_cmd('list_vol_snapshots',
351
snap = self._xml_parse_helper(xml_tree, 'snapshots',
352
('display-name', snap_name))
354
return snap.findtext('name')
358
def _get_pool_capacity(self, pool_name):
359
"""Return pool's total and available capacities."""
360
xml_tree = self.vpsa.send_cmd('list_pools')
361
pool = self._xml_parse_helper(xml_tree, 'pools',
364
total = int(pool.findtext('capacity'))
365
free = int(float(pool.findtext('available-capacity')))
366
LOG.debug(_('Pool %(name)s: %(total)sGB total, %(free)sGB free'),
367
{'name': pool_name, 'total': total, 'free': free})
370
return ('infinite', 'infinite')
304
372
def _get_active_controller_details(self):
305
373
"""Return details of VPSA's active controller."""
375
443
self.vpsa.send_cmd('delete_volume', vpsa_vol=vpsa_vol)
445
def create_snapshot(self, snapshot):
446
"""Creates a snapshot."""
448
LOG.debug(_('Create snapshot: %s'), snapshot['name'])
450
# Retrieve the CG name for the base volume
451
volume_name = self.configuration.zadara_vol_name_template\
452
% snapshot['volume_name']
453
cg_name = self._get_volume_cg_name(volume_name)
455
msg = _('Volume %(name)s not found') % {'name': volume_name}
457
raise exception.VolumeNotFound(volume_id=volume_name)
459
self.vpsa.send_cmd('create_snapshot',
461
snap_name=snapshot['name'])
463
def delete_snapshot(self, snapshot):
464
"""Deletes a snapshot."""
466
LOG.debug(_('Delete snapshot: %s'), snapshot['name'])
468
# Retrieve the CG name for the base volume
469
volume_name = self.configuration.zadara_vol_name_template\
470
% snapshot['volume_name']
471
cg_name = self._get_volume_cg_name(volume_name)
473
# If the volume isn't present, then don't attempt to delete
474
LOG.warning(_("snapshot: original volume %s not found, "
475
"skipping delete operation")
476
% snapshot['volume_name'])
479
snap_id = self._get_snap_id(cg_name, snapshot['name'])
481
# If the snapshot isn't present, then don't attempt to delete
482
LOG.warning(_("snapshot: snapshot %s not found, "
483
"skipping delete operation")
487
self.vpsa.send_cmd('delete_snapshot',
490
def create_volume_from_snapshot(self, volume, snapshot):
491
"""Creates a volume from a snapshot."""
493
LOG.debug(_('Creating volume from snapshot: %s') % snapshot['name'])
495
# Retrieve the CG name for the base volume
496
volume_name = self.configuration.zadara_vol_name_template\
497
% snapshot['volume_name']
498
cg_name = self._get_volume_cg_name(volume_name)
500
msg = _('Volume %(name)s not found') % {'name': volume_name}
502
raise exception.VolumeNotFound(volume_id=volume_name)
504
snap_id = self._get_snap_id(cg_name, snapshot['name'])
506
msg = _('Snapshot %(name)s not found') % {'name': snapshot['name']}
508
raise exception.VolumeNotFound(volume_id=snapshot['name'])
510
self.vpsa.send_cmd('create_clone_from_snap',
512
name=self.configuration.zadara_vol_name_template
516
def create_cloned_volume(self, volume, src_vref):
517
"""Creates a clone of the specified volume."""
519
LOG.debug(_('Creating clone of volume: %s') % src_vref['name'])
521
# Retrieve the CG name for the base volume
522
volume_name = self.configuration.zadara_vol_name_template\
524
cg_name = self._get_volume_cg_name(volume_name)
526
msg = _('Volume %(name)s not found') % {'name': volume_name}
528
raise exception.VolumeNotFound(volume_id=volume_name)
530
self.vpsa.send_cmd('create_clone',
532
name=self.configuration.zadara_vol_name_template
535
def extend_volume(self, volume, new_size):
536
"""Extend an existing volume."""
538
name = self.configuration.zadara_vol_name_template % volume['name']
539
(vpsa_vol, size) = self._get_vpsa_volume_name_and_size(name)
541
msg = _('Volume %(name)s could not be found. '
542
'It might be already deleted') % {'name': name}
544
raise exception.VolumeNotFound(volume_id=name)
547
raise exception.InvalidInput(
548
reason='%s < current size %s' % (new_size, size))
550
expand_size = new_size - size
551
self.vpsa.send_cmd('expand_volume',
377
555
def create_export(self, context, volume):
378
556
"""Irrelevant for VPSA volumes. Export created during attachment."""
469
647
self.vpsa.send_cmd('detach_volume',
470
648
vpsa_srv=vpsa_srv,
471
649
vpsa_vol=vpsa_vol)
651
def get_volume_stats(self, refresh=False):
653
If 'refresh' is True, run update the stats first.
656
self._update_volume_stats()
660
def _update_volume_stats(self):
661
"""Retrieve stats info from volume group."""
663
LOG.debug(_("Updating volume stats"))
666
backend_name = self.configuration.safe_get('volume_backend_name')
667
data["volume_backend_name"] = backend_name or self.__class__.__name__
668
data["vendor_name"] = 'Zadara Storage'
669
data["driver_version"] = self.VERSION
670
data["storage_protocol"] = 'iSCSI'
671
data['reserved_percentage'] = self.configuration.reserved_percentage
672
data['QoS_support'] = False
674
(total, free) = self._get_pool_capacity(self.configuration.
675
zadara_vpsa_poolname)
676
data['total_capacity_gb'] = total
677
data['free_capacity_gb'] = free