37
38
:createVolume: (creates the volume)
40
:deleteVolume: (deletes the volume)
42
:modifyVolume: (extends the volume)
44
:createSnapshot: (creates the snapshot)
46
:deleteSnapshot: (deletes the snapshot)
48
:cloneSnapshot: (creates the volume from a snapshot)
39
50
:getVolumeInfo: (to discover the IQN etc)
52
:getSnapshotInfo: (to discover the IQN etc)
41
54
:getClusterInfo: (to discover the iSCSI target IP address)
43
56
:assignVolumeChap: (exports it with CHAP security)
49
62
share the volume using CHAP at volume creation time. Then the mount need
50
63
only use those CHAP credentials, so can take place exclusively in the
67
1.0.0 - Initial driver
68
1.1.0 - Added create/delete snapshot, extend volume, create volume
69
from snapshot support.
56
76
def __init__(self, *args, **kwargs):
60
80
def _cliq_run(self, verb, cliq_args, check_exit_code=True):
61
81
"""Runs a CLIQ command over SSH, without doing any result parsing"""
63
83
for k, v in cliq_args.items():
64
cliq_arg_strings.append(" %s=%s" % (k, v))
65
cmd = verb + ''.join(cliq_arg_strings)
84
cmd_list.append("%s=%s" % (k, v))
67
return self._run_ssh(cmd, check_exit_code)
86
return self._run_ssh(cmd_list, check_exit_code)
69
88
def _cliq_run_xml(self, verb, cliq_args, check_cliq_result=True):
70
89
"""Runs a CLIQ command over SSH, parsing and checking the output"""
173
192
'volume_attributes': volume_attributes})
174
193
return volume_attributes
195
def _cliq_get_snapshot_info(self, snapshot_name):
196
"""Gets the snapshot info, including IQN"""
198
cliq_args['snapshotName'] = snapshot_name
199
result_xml = self._cliq_run_xml("getSnapshotInfo", cliq_args)
201
# Result looks like this:
202
# <gauche version="1.0">
203
# <response description="Operation succeeded." name="CliqSuccess"
204
# processingTime="87" result="0">
205
# <snapshot applicationManaged="false" autogrowPages="32768"
206
# automatic="false" availability="online" bytesWritten="0"
207
# clusterName="CloudCluster1" created="2013-08-26T07:03:44Z"
208
# deleting="false" description="" groupName="CloudMgmtGroup1"
209
# id="730" initialQuota="536870912" isPrimary="true"
210
# iscsiIqn="iqn.2003-10.com.lefthandnetworks:cloudmgmtgroup1:73"
211
# md5="a64b4f850539c07fb5ce3cee5db1fcce" minReplication="1"
212
# name="snapshot-7849288e-e5e8-42cb-9687-9af5355d674b"
213
# replication="2" reserveQuota="536870912" scheduleId="0"
214
# scratchQuota="4194304" scratchWritten="0"
215
# serialNumber="a64b4f850539c07fb5ce3cee5db1fcce00000000000002da"
216
# size="2147483648" stridePages="32"
217
# volumeSerial="a64b4f850539c07fb5ce3cee5db1fcce00000000000002d">
218
# <status description="OK" value="2"/>
219
# <permission access="rw"
220
# authGroup="api-34281B815713B78-(trimmed)51ADD4B7030853AA7"
221
# chapName="chapusername" chapRequired="true" id="25369"
222
# initiatorSecret="" iqn="" iscsiEnabled="true"
223
# loadBalance="true" targetSecret="supersecret"/>
228
# Flatten the nodes into a dictionary; use prefixes to avoid collisions
229
snapshot_attributes = {}
231
snapshot_node = result_xml.find("response/snapshot")
232
for k, v in snapshot_node.attrib.items():
233
snapshot_attributes["snapshot." + k] = v
235
status_node = snapshot_node.find("status")
236
if status_node is not None:
237
for k, v in status_node.attrib.items():
238
snapshot_attributes["status." + k] = v
240
# We only consider the first permission node
241
permission_node = snapshot_node.find("permission")
242
if permission_node is not None:
243
for k, v in status_node.attrib.items():
244
snapshot_attributes["permission." + k] = v
246
LOG.debug(_("Snapshot info: %(name)s => %(attributes)s") %
247
{'name': snapshot_name, 'attributes': snapshot_attributes})
248
return snapshot_attributes
176
250
def create_volume(self, volume):
177
251
"""Creates a volume."""
192
266
self._cliq_run_xml("createVolume", cliq_args)
194
volume_info = self._cliq_get_volume_info(volume['name'])
195
cluster_name = volume_info['volume.clusterName']
196
iscsi_iqn = volume_info['volume.iscsiIqn']
198
#TODO(justinsb): Is this always 1? Does it matter?
199
cluster_interface = '1'
201
if not self.cluster_vip:
202
self.cluster_vip = self._cliq_get_cluster_vip(cluster_name)
203
iscsi_portal = self.cluster_vip + ":3260," + cluster_interface
207
# NOTE(jdg): LH volumes always at lun 0 ?
208
model_update['provider_location'] = ("%s %s %s" %
268
return self._get_model_update(volume['name'])
270
def extend_volume(self, volume, new_size):
271
"""Extend the size of an existing volume."""
273
cliq_args['volumeName'] = volume['name']
274
cliq_args['size'] = '%sGB' % new_size
276
self._cliq_run_xml("modifyVolume", cliq_args)
215
278
def create_volume_from_snapshot(self, volume, snapshot):
216
279
"""Creates a volume from a snapshot."""
217
raise NotImplementedError()
281
cliq_args['snapshotName'] = snapshot['name']
282
cliq_args['volumeName'] = volume['name']
284
self._cliq_run_xml("cloneSnapshot", cliq_args)
286
return self._get_model_update(volume['name'])
219
288
def create_snapshot(self, snapshot):
220
289
"""Creates a snapshot."""
221
raise NotImplementedError()
291
cliq_args['snapshotName'] = snapshot['name']
292
cliq_args['volumeName'] = snapshot['volume_name']
293
cliq_args['inheritAccess'] = 1
294
self._cliq_run_xml("createSnapshot", cliq_args)
223
296
def delete_volume(self, volume):
224
297
"""Deletes a volume."""
227
300
cliq_args['prompt'] = 'false' # Don't confirm
229
302
volume_info = self._cliq_get_volume_info(volume['name'])
230
except exception.ProcessExecutionError:
231
LOG.error("Volume did not exist. It will not be deleted")
303
except processutils.ProcessExecutionError:
304
LOG.error_("Volume did not exist. It will not be deleted")
233
306
self._cliq_run_xml("deleteVolume", cliq_args)
308
def delete_snapshot(self, snapshot):
309
"""Deletes a snapshot."""
311
cliq_args['snapshotName'] = snapshot['name']
312
cliq_args['prompt'] = 'false' # Don't confirm
314
volume_info = self._cliq_get_snapshot_info(snapshot['name'])
315
except processutils.ProcessExecutionError:
316
LOG.error_("Snapshot did not exist. It will not be deleted")
318
self._cliq_run_xml("deleteSnapshot", cliq_args)
235
320
def local_path(self, volume):
236
321
msg = _("local_path not supported")
237
322
raise exception.VolumeBackendAPIException(data=msg)
282
367
cliq_args['initiator'] = connector['initiator']
283
368
self._cliq_run_xml("createServer", cliq_args)
370
def _get_model_update(self, volume_name):
371
volume_info = self._cliq_get_volume_info(volume_name)
372
cluster_name = volume_info['volume.clusterName']
373
iscsi_iqn = volume_info['volume.iscsiIqn']
375
# TODO(justinsb): Is this always 1? Does it matter?
376
cluster_interface = '1'
378
if not self.cluster_vip:
379
self.cluster_vip = self._cliq_get_cluster_vip(cluster_name)
380
iscsi_portal = self.cluster_vip + ":3260," + cluster_interface
384
# NOTE(jdg): LH volumes always at lun 0 ?
385
model_update['provider_location'] = ("%s %s %s" %
285
391
def terminate_connection(self, volume, connector, **kwargs):
286
392
"""Unassign the volume from the host."""
300
406
backend_name = self.configuration.safe_get('volume_backend_name')
301
407
data['volume_backend_name'] = backend_name or self.__class__.__name__
302
data['driver_version'] = '1.0'
408
data['driver_version'] = self.VERSION
303
409
data['reserved_percentage'] = 0
304
410
data['storage_protocol'] = 'iSCSI'
305
411
data['vendor_name'] = 'Hewlett-Packard'