~ubuntu-branches/ubuntu/utopic/cinder/utopic

« back to all changes in this revision

Viewing changes to cinder/volume/drivers/san/hp_lefthand.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, James Page, Adam Gandelman, Chuck Short
  • Date: 2013-09-08 21:09:46 UTC
  • mfrom: (1.1.18)
  • Revision ID: package-import@ubuntu.com-20130908210946-3dbzq1jy5uji4wad
Tags: 1:2013.2~b3-0ubuntu1
[ James Page ]
* d/control: Switch ceph-common -> python-ceph inline with upstream
  refactoring of Ceph RBD driver, move to Suggests of python-cinder.
  (LP: #1190791). 

[ Adam Gandelman ]
* debian/patches/avoid_paramiko_vers_depends.patch: Dropped, no longer
  required.
* Add minimum requirement python-greenlet (>= 0.3.2).
* Add minimum requirement python-eventlet (>= 0.12.0).
* Add minimum requirement python-paramiko (>= 1.8).

[ Chuck Short ]
* New upstream release.
* debian/patches/skip-sqlachemy-failures.patch: Skip testfailures
  with sqlalchemy 0.8 until they are fixed upstream.
* debian/control: Add python-babel to build-depends.
* debian/control: Add python-novaclient to build-depends.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
from cinder import exception
23
23
from cinder.openstack.common import log as logging
 
24
from cinder.openstack.common import processutils
24
25
from cinder.volume.drivers.san.san import SanISCSIDriver
25
26
 
26
27
 
36
37
 
37
38
    :createVolume:    (creates the volume)
38
39
 
 
40
    :deleteVolume:    (deletes the volume)
 
41
 
 
42
    :modifyVolume:    (extends the volume)
 
43
 
 
44
    :createSnapshot:    (creates the snapshot)
 
45
 
 
46
    :deleteSnapshot:    (deletes the snapshot)
 
47
 
 
48
    :cloneSnapshot:    (creates the volume from a snapshot)
 
49
 
39
50
    :getVolumeInfo:    (to discover the IQN etc)
40
51
 
 
52
    :getSnapshotInfo:    (to discover the IQN etc)
 
53
 
41
54
    :getClusterInfo:    (to discover the iSCSI target IP address)
42
55
 
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
51
64
    compute layer.
 
65
 
 
66
    Version history:
 
67
        1.0.0 - Initial driver
 
68
        1.1.0 - Added create/delete snapshot, extend volume, create volume
 
69
                from snapshot support.
52
70
    """
53
71
 
 
72
    VERSION = "1.1.0"
 
73
 
54
74
    device_stats = {}
55
75
 
56
76
    def __init__(self, *args, **kwargs):
59
79
 
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"""
62
 
        cliq_arg_strings = []
 
82
        cmd_list = [verb]
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))
66
85
 
67
 
        return self._run_ssh(cmd, check_exit_code)
 
86
        return self._run_ssh(cmd_list, check_exit_code)
68
87
 
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"""
127
146
        result_xml = self._cliq_run_xml("getVolumeInfo", cliq_args)
128
147
 
129
148
        # Result looks like this:
130
 
        #<gauche version="1.0">
 
149
        # <gauche version="1.0">
131
150
        #  <response description="Operation succeeded." name="CliqSuccess"
132
151
        #            processingTime="87" result="0">
133
152
        #    <volume autogrowPages="4" availability="online" blockSize="1024"
148
167
        #            loadBalance="true" targetSecret="supersecret"/>
149
168
        #    </volume>
150
169
        #  </response>
151
 
        #</gauche>
 
170
        # </gauche>
152
171
 
153
172
        # Flatten the nodes into a dictionary; use prefixes to avoid collisions
154
173
        volume_attributes = {}
173
192
                   'volume_attributes': volume_attributes})
174
193
        return volume_attributes
175
194
 
 
195
    def _cliq_get_snapshot_info(self, snapshot_name):
 
196
        """Gets the snapshot info, including IQN"""
 
197
        cliq_args = {}
 
198
        cliq_args['snapshotName'] = snapshot_name
 
199
        result_xml = self._cliq_run_xml("getSnapshotInfo", cliq_args)
 
200
 
 
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"/>
 
224
        #    </snapshot>
 
225
        #  </response>
 
226
        # </gauche>
 
227
 
 
228
        # Flatten the nodes into a dictionary; use prefixes to avoid collisions
 
229
        snapshot_attributes = {}
 
230
 
 
231
        snapshot_node = result_xml.find("response/snapshot")
 
232
        for k, v in snapshot_node.attrib.items():
 
233
            snapshot_attributes["snapshot." + k] = v
 
234
 
 
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
 
239
 
 
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
 
245
 
 
246
        LOG.debug(_("Snapshot info: %(name)s => %(attributes)s") %
 
247
                  {'name': snapshot_name, 'attributes': snapshot_attributes})
 
248
        return snapshot_attributes
 
249
 
176
250
    def create_volume(self, volume):
177
251
        """Creates a volume."""
178
252
        cliq_args = {}
191
265
 
192
266
        self._cliq_run_xml("createVolume", cliq_args)
193
267
 
194
 
        volume_info = self._cliq_get_volume_info(volume['name'])
195
 
        cluster_name = volume_info['volume.clusterName']
196
 
        iscsi_iqn = volume_info['volume.iscsiIqn']
197
 
 
198
 
        #TODO(justinsb): Is this always 1? Does it matter?
199
 
        cluster_interface = '1'
200
 
 
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
204
 
 
205
 
        model_update = {}
206
 
 
207
 
        # NOTE(jdg): LH volumes always at lun 0 ?
208
 
        model_update['provider_location'] = ("%s %s %s" %
209
 
                                             (iscsi_portal,
210
 
                                              iscsi_iqn,
211
 
                                              0))
212
 
 
213
 
        return model_update
 
268
        return self._get_model_update(volume['name'])
 
269
 
 
270
    def extend_volume(self, volume, new_size):
 
271
        """Extend the size of an existing volume."""
 
272
        cliq_args = {}
 
273
        cliq_args['volumeName'] = volume['name']
 
274
        cliq_args['size'] = '%sGB' % new_size
 
275
 
 
276
        self._cliq_run_xml("modifyVolume", cliq_args)
214
277
 
215
278
    def create_volume_from_snapshot(self, volume, snapshot):
216
279
        """Creates a volume from a snapshot."""
217
 
        raise NotImplementedError()
 
280
        cliq_args = {}
 
281
        cliq_args['snapshotName'] = snapshot['name']
 
282
        cliq_args['volumeName'] = volume['name']
 
283
 
 
284
        self._cliq_run_xml("cloneSnapshot", cliq_args)
 
285
 
 
286
        return self._get_model_update(volume['name'])
218
287
 
219
288
    def create_snapshot(self, snapshot):
220
289
        """Creates a snapshot."""
221
 
        raise NotImplementedError()
 
290
        cliq_args = {}
 
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)
222
295
 
223
296
    def delete_volume(self, volume):
224
297
        """Deletes a volume."""
227
300
        cliq_args['prompt'] = 'false'  # Don't confirm
228
301
        try:
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")
232
305
            return
233
306
        self._cliq_run_xml("deleteVolume", cliq_args)
234
307
 
 
308
    def delete_snapshot(self, snapshot):
 
309
        """Deletes a snapshot."""
 
310
        cliq_args = {}
 
311
        cliq_args['snapshotName'] = snapshot['name']
 
312
        cliq_args['prompt'] = 'false'  # Don't confirm
 
313
        try:
 
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")
 
317
            return
 
318
        self._cliq_run_xml("deleteSnapshot", cliq_args)
 
319
 
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)
284
369
 
 
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']
 
374
 
 
375
        # TODO(justinsb): Is this always 1? Does it matter?
 
376
        cluster_interface = '1'
 
377
 
 
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
 
381
 
 
382
        model_update = {}
 
383
 
 
384
        # NOTE(jdg): LH volumes always at lun 0 ?
 
385
        model_update['provider_location'] = ("%s %s %s" %
 
386
                                             (iscsi_portal,
 
387
                                              iscsi_iqn,
 
388
                                              0))
 
389
        return model_update
 
390
 
285
391
    def terminate_connection(self, volume, connector, **kwargs):
286
392
        """Unassign the volume from the host."""
287
393
        cliq_args = {}
299
405
        data = {}
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'