~ubuntu-branches/ubuntu/trusty/cinder/trusty

« back to all changes in this revision

Viewing changes to cinder/volume/drivers/lvm.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Yolanda Robla Mota, James Page, Chuck Short
  • Date: 2013-02-22 10:45:17 UTC
  • mfrom: (1.1.11)
  • Revision ID: package-import@ubuntu.com-20130222104517-ng3r6ace9vi4m869
Tags: 2013.1.g3-0ubuntu1
[ Yolanda Robla Mota ]
* d/control: Add BD on python-hp3parclient.
* d/patches: Drop patches related to disabling hp3parclient.

[ James Page ]
* d/control: Add Suggests: python-hp3parclient for python-cinder.
* d/control: Add BD on python-oslo-config.
* d/*: Wrapped and sorted.

[ Chuck Short ]
* New upstream release.
* debian/rules, debian/cinder-volumes.install: 
  - Fail if binaries are missing and install missing binaries.
* debian/patches/fix-ubuntu-tests.patch: Fix failing tests.
* debian/control: Add python-rtslib and python-mock.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 United States Government as represented by the
 
4
# Administrator of the National Aeronautics and Space Administration.
 
5
# All Rights Reserved.
 
6
#
 
7
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
#    not use this file except in compliance with the License. You may obtain
 
9
#    a copy of the License at
 
10
#
 
11
#         http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
13
#    Unless required by applicable law or agreed to in writing, software
 
14
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
#    License for the specific language governing permissions and limitations
 
17
#    under the License.
 
18
"""
 
19
Driver for Linux servers running LVM.
 
20
 
 
21
"""
 
22
 
 
23
import math
 
24
import os
 
25
import re
 
26
 
 
27
from oslo.config import cfg
 
28
 
 
29
from cinder import exception
 
30
from cinder import flags
 
31
from cinder.image import image_utils
 
32
from cinder.openstack.common import log as logging
 
33
from cinder import utils
 
34
from cinder.volume import driver
 
35
from cinder.volume import iscsi
 
36
 
 
37
LOG = logging.getLogger(__name__)
 
38
 
 
39
volume_opts = [
 
40
    cfg.StrOpt('volume_group',
 
41
               default='cinder-volumes',
 
42
               help='Name for the VG that will contain exported volumes'),
 
43
    cfg.StrOpt('volume_clear',
 
44
               default='zero',
 
45
               help='Method used to wipe old volumes (valid options are: '
 
46
                    'none, zero, shred)'),
 
47
    cfg.IntOpt('volume_clear_size',
 
48
               default=0,
 
49
               help='Size in MiB to wipe at start of old volumes. 0 => all'),
 
50
    cfg.StrOpt('pool_size',
 
51
               default=None,
 
52
               help='Size of thin provisioning pool '
 
53
                    '(None uses entire cinder VG)'),
 
54
    cfg.IntOpt('lvm_mirrors',
 
55
               default=0,
 
56
               help='If set, create lvms with multiple mirrors. Note that '
 
57
                    'this requires lvm_mirrors + 2 pvs with available space'),
 
58
]
 
59
 
 
60
FLAGS = flags.FLAGS
 
61
FLAGS.register_opts(volume_opts)
 
62
 
 
63
 
 
64
class LVMVolumeDriver(driver.VolumeDriver):
 
65
    """Executes commands relating to Volumes."""
 
66
    def __init__(self, *args, **kwargs):
 
67
        super(LVMVolumeDriver, self).__init__(*args, **kwargs)
 
68
        self.configuration.append_config_values(volume_opts)
 
69
 
 
70
    def check_for_setup_error(self):
 
71
        """Returns an error if prerequisites aren't met"""
 
72
        out, err = self._execute('vgs', '--noheadings', '-o', 'name',
 
73
                                 run_as_root=True)
 
74
        volume_groups = out.split()
 
75
        if self.configuration.volume_group not in volume_groups:
 
76
            exception_message = (_("volume group %s doesn't exist")
 
77
                                 % self.configuration.volume_group)
 
78
            raise exception.VolumeBackendAPIException(data=exception_message)
 
79
 
 
80
    def _create_volume(self, volume_name, sizestr):
 
81
        cmd = ['lvcreate', '-L', sizestr, '-n', volume_name,
 
82
               self.configuration.volume_group]
 
83
        if self.configuration.lvm_mirrors:
 
84
            cmd += ['-m', self.configuration.lvm_mirrors, '--nosync']
 
85
            terras = int(sizestr[:-1]) / 1024.0
 
86
            if terras >= 1.5:
 
87
                rsize = int(2 ** math.ceil(math.log(terras) / math.log(2)))
 
88
                # NOTE(vish): Next power of two for region size. See:
 
89
                #             http://red.ht/U2BPOD
 
90
                cmd += ['-R', str(rsize)]
 
91
 
 
92
        self._try_execute(*cmd, run_as_root=True)
 
93
 
 
94
    def _copy_volume(self, srcstr, deststr, size_in_g, clearing=False):
 
95
        # Use O_DIRECT to avoid thrashing the system buffer cache
 
96
        extra_flags = ['iflag=direct', 'oflag=direct']
 
97
 
 
98
        # Check whether O_DIRECT is supported
 
99
        try:
 
100
            self._execute('dd', 'count=0', 'if=%s' % srcstr, 'of=%s' % deststr,
 
101
                          *extra_flags, run_as_root=True)
 
102
        except exception.ProcessExecutionError:
 
103
            extra_flags = []
 
104
 
 
105
        # If the volume is being unprovisioned then
 
106
        # request the data is persisted before returning,
 
107
        # so that it's not discarded from the cache.
 
108
        if clearing and not extra_flags:
 
109
            extra_flags.append('conv=fdatasync')
 
110
 
 
111
        # Perform the copy
 
112
        self._execute('dd', 'if=%s' % srcstr, 'of=%s' % deststr,
 
113
                      'count=%d' % (size_in_g * 1024), 'bs=1M',
 
114
                      *extra_flags, run_as_root=True)
 
115
 
 
116
    def _volume_not_present(self, volume_name):
 
117
        path_name = '%s/%s' % (self.configuration.volume_group, volume_name)
 
118
        try:
 
119
            self._try_execute('lvdisplay', path_name, run_as_root=True)
 
120
        except Exception as e:
 
121
            # If the volume isn't present
 
122
            return True
 
123
        return False
 
124
 
 
125
    def _delete_volume(self, volume, size_in_g):
 
126
        """Deletes a logical volume."""
 
127
        # zero out old volumes to prevent data leaking between users
 
128
        # TODO(ja): reclaiming space should be done lazy and low priority
 
129
        dev_path = self.local_path(volume)
 
130
        if os.path.exists(dev_path):
 
131
            self.clear_volume(volume)
 
132
 
 
133
        self._try_execute('lvremove', '-f', "%s/%s" %
 
134
                          (self.configuration.volume_group,
 
135
                           self._escape_snapshot(volume['name'])),
 
136
                          run_as_root=True)
 
137
 
 
138
    def _sizestr(self, size_in_g):
 
139
        if int(size_in_g) == 0:
 
140
            return '100M'
 
141
        return '%sG' % size_in_g
 
142
 
 
143
    # Linux LVM reserves name that starts with snapshot, so that
 
144
    # such volume name can't be created. Mangle it.
 
145
    def _escape_snapshot(self, snapshot_name):
 
146
        if not snapshot_name.startswith('snapshot'):
 
147
            return snapshot_name
 
148
        return '_' + snapshot_name
 
149
 
 
150
    def create_volume(self, volume):
 
151
        """Creates a logical volume. Can optionally return a Dictionary of
 
152
        changes to the volume object to be persisted."""
 
153
        self._create_volume(volume['name'], self._sizestr(volume['size']))
 
154
 
 
155
    def create_volume_from_snapshot(self, volume, snapshot):
 
156
        """Creates a volume from a snapshot."""
 
157
        self._create_volume(volume['name'], self._sizestr(volume['size']))
 
158
        self._copy_volume(self.local_path(snapshot), self.local_path(volume),
 
159
                          snapshot['volume_size'])
 
160
 
 
161
    def delete_volume(self, volume):
 
162
        """Deletes a logical volume."""
 
163
        if self._volume_not_present(volume['name']):
 
164
            # If the volume isn't present, then don't attempt to delete
 
165
            return True
 
166
 
 
167
        # TODO(yamahata): lvm can't delete origin volume only without
 
168
        # deleting derived snapshots. Can we do something fancy?
 
169
        out, err = self._execute('lvdisplay', '--noheading',
 
170
                                 '-C', '-o', 'Attr',
 
171
                                 '%s/%s' % (self.configuration.volume_group,
 
172
                                            volume['name']),
 
173
                                 run_as_root=True)
 
174
        # fake_execute returns None resulting unit test error
 
175
        if out:
 
176
            out = out.strip()
 
177
            if (out[0] == 'o') or (out[0] == 'O'):
 
178
                raise exception.VolumeIsBusy(volume_name=volume['name'])
 
179
 
 
180
        self._delete_volume(volume, volume['size'])
 
181
 
 
182
    def clear_volume(self, volume):
 
183
        """unprovision old volumes to prevent data leaking between users."""
 
184
 
 
185
        vol_path = self.local_path(volume)
 
186
        size_in_g = volume.get('size')
 
187
        size_in_m = self.configuration.volume_clear_size
 
188
 
 
189
        if not size_in_g:
 
190
            return
 
191
 
 
192
        if self.configuration.volume_clear == 'none':
 
193
            return
 
194
 
 
195
        LOG.info(_("Performing secure delete on volume: %s") % volume['id'])
 
196
 
 
197
        if self.configuration.volume_clear == 'zero':
 
198
            if size_in_m == 0:
 
199
                return self._copy_volume('/dev/zero', vol_path, size_in_g,
 
200
                                         clearing=True)
 
201
            else:
 
202
                clear_cmd = ['shred', '-n0', '-z', '-s%dMiB' % size_in_m]
 
203
        elif self.configuration.volume_clear == 'shred':
 
204
            clear_cmd = ['shred', '-n3']
 
205
            if size_in_m:
 
206
                clear_cmd.append('-s%dMiB' % size_in_m)
 
207
        else:
 
208
            LOG.error(_("Error unrecognized volume_clear option: %s"),
 
209
                      self.configuration.volume_clear)
 
210
            return
 
211
 
 
212
        clear_cmd.append(vol_path)
 
213
        self._execute(*clear_cmd, run_as_root=True)
 
214
 
 
215
    def create_snapshot(self, snapshot):
 
216
        """Creates a snapshot."""
 
217
        orig_lv_name = "%s/%s" % (self.configuration.volume_group,
 
218
                                  snapshot['volume_name'])
 
219
        self._try_execute('lvcreate', '-L',
 
220
                          self._sizestr(snapshot['volume_size']),
 
221
                          '--name', self._escape_snapshot(snapshot['name']),
 
222
                          '--snapshot', orig_lv_name, run_as_root=True)
 
223
 
 
224
    def delete_snapshot(self, snapshot):
 
225
        """Deletes a snapshot."""
 
226
        if self._volume_not_present(self._escape_snapshot(snapshot['name'])):
 
227
            # If the snapshot isn't present, then don't attempt to delete
 
228
            return True
 
229
 
 
230
        # TODO(yamahata): zeroing out the whole snapshot triggers COW.
 
231
        # it's quite slow.
 
232
        self._delete_volume(snapshot, snapshot['volume_size'])
 
233
 
 
234
    def local_path(self, volume):
 
235
        # NOTE(vish): stops deprecation warning
 
236
        escaped_group = self.configuration.volume_group.replace('-', '--')
 
237
        escaped_name = self._escape_snapshot(volume['name']).replace('-', '--')
 
238
        return "/dev/mapper/%s-%s" % (escaped_group, escaped_name)
 
239
 
 
240
    def copy_image_to_volume(self, context, volume, image_service, image_id):
 
241
        """Fetch the image from image_service and write it to the volume."""
 
242
        image_utils.fetch_to_raw(context,
 
243
                                 image_service,
 
244
                                 image_id,
 
245
                                 self.local_path(volume))
 
246
 
 
247
    def copy_volume_to_image(self, context, volume, image_service, image_meta):
 
248
        """Copy the volume to the specified image."""
 
249
        image_utils.upload_volume(context,
 
250
                                  image_service,
 
251
                                  image_meta,
 
252
                                  self.local_path(volume))
 
253
 
 
254
    def create_cloned_volume(self, volume, src_vref):
 
255
        """Creates a clone of the specified volume."""
 
256
        LOG.info(_('Creating clone of volume: %s') % src_vref['id'])
 
257
        volume_name = FLAGS.volume_name_template % src_vref['id']
 
258
        temp_id = 'tmp-snap-%s' % src_vref['id']
 
259
        temp_snapshot = {'volume_name': volume_name,
 
260
                         'size': src_vref['size'],
 
261
                         'volume_size': src_vref['size'],
 
262
                         'name': 'clone-snap-%s' % src_vref['id'],
 
263
                         'id': temp_id}
 
264
        self.create_snapshot(temp_snapshot)
 
265
        self._create_volume(volume['name'], self._sizestr(volume['size']))
 
266
        try:
 
267
            self._copy_volume(self.local_path(temp_snapshot),
 
268
                              self.local_path(volume),
 
269
                              src_vref['size'])
 
270
        finally:
 
271
            self.delete_snapshot(temp_snapshot)
 
272
 
 
273
    def clone_image(self, volume, image_location):
 
274
        return False
 
275
 
 
276
    def backup_volume(self, context, backup, backup_service):
 
277
        """Create a new backup from an existing volume."""
 
278
        volume = self.db.volume_get(context, backup['volume_id'])
 
279
        volume_path = self.local_path(volume)
 
280
        with utils.temporary_chown(volume_path):
 
281
            with utils.file_open(volume_path) as volume_file:
 
282
                backup_service.backup(backup, volume_file)
 
283
 
 
284
    def restore_backup(self, context, backup, volume, backup_service):
 
285
        """Restore an existing backup to a new or existing volume."""
 
286
        volume_path = self.local_path(volume)
 
287
        with utils.temporary_chown(volume_path):
 
288
            with utils.file_open(volume_path, 'wb') as volume_file:
 
289
                backup_service.restore(backup, volume['id'], volume_file)
 
290
 
 
291
 
 
292
class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
 
293
    """Executes commands relating to ISCSI volumes.
 
294
 
 
295
    We make use of model provider properties as follows:
 
296
 
 
297
    ``provider_location``
 
298
      if present, contains the iSCSI target information in the same
 
299
      format as an ietadm discovery
 
300
      i.e. '<ip>:<port>,<portal> <target IQN>'
 
301
 
 
302
    ``provider_auth``
 
303
      if present, contains a space-separated triple:
 
304
      '<auth method> <auth username> <auth password>'.
 
305
      `CHAP` is the only auth_method in use at the moment.
 
306
    """
 
307
 
 
308
    def __init__(self, *args, **kwargs):
 
309
        self.tgtadm = iscsi.get_target_admin()
 
310
        super(LVMISCSIDriver, self).__init__(*args, **kwargs)
 
311
 
 
312
    def set_execute(self, execute):
 
313
        super(LVMISCSIDriver, self).set_execute(execute)
 
314
        self.tgtadm.set_execute(execute)
 
315
 
 
316
    def ensure_export(self, context, volume):
 
317
        """Synchronously recreates an export for a logical volume."""
 
318
        # NOTE(jdg): tgtadm doesn't use the iscsi_targets table
 
319
        # TODO(jdg): In the future move all of the dependent stuff into the
 
320
        # cooresponding target admin class
 
321
 
 
322
        if isinstance(self.tgtadm, iscsi.LioAdm):
 
323
            try:
 
324
                volume_info = self.db.volume_get(context, volume['id'])
 
325
                (auth_method,
 
326
                 auth_user,
 
327
                 auth_pass) = volume_info['provider_auth'].split(' ', 3)
 
328
                chap_auth = self._iscsi_authentication(auth_method,
 
329
                                                       auth_user,
 
330
                                                       auth_pass)
 
331
            except exception.NotFound:
 
332
                LOG.debug("volume_info:", volume_info)
 
333
                LOG.info(_("Skipping ensure_export. No iscsi_target "
 
334
                           "provision for volume: %s"), volume['id'])
 
335
                return
 
336
 
 
337
            iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
 
338
            volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
 
339
            iscsi_target = 1
 
340
 
 
341
            self.tgtadm.create_iscsi_target(iscsi_name, iscsi_target,
 
342
                                            0, volume_path, chap_auth,
 
343
                                            check_exit_code=False)
 
344
            return
 
345
 
 
346
        if not isinstance(self.tgtadm, iscsi.TgtAdm):
 
347
            try:
 
348
                iscsi_target = self.db.volume_get_iscsi_target_num(
 
349
                    context,
 
350
                    volume['id'])
 
351
            except exception.NotFound:
 
352
                LOG.info(_("Skipping ensure_export. No iscsi_target "
 
353
                           "provisioned for volume: %s"), volume['id'])
 
354
                return
 
355
        else:
 
356
            iscsi_target = 1  # dummy value when using TgtAdm
 
357
 
 
358
        chap_auth = None
 
359
 
 
360
        # Check for https://bugs.launchpad.net/cinder/+bug/1065702
 
361
        old_name = None
 
362
        volume_name = volume['name']
 
363
        if (volume['provider_location'] is not None and
 
364
                volume['name'] not in volume['provider_location']):
 
365
 
 
366
            msg = _('Detected inconsistency in provider_location id')
 
367
            LOG.debug(msg)
 
368
            old_name = self._fix_id_migration(context, volume)
 
369
            if 'in-use' in volume['status']:
 
370
                volume_name = old_name
 
371
                old_name = None
 
372
 
 
373
        iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
 
374
                               volume_name)
 
375
        volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
 
376
                                      volume_name)
 
377
 
 
378
        # NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
 
379
        # should clean this all up at some point in the future
 
380
        self.tgtadm.create_iscsi_target(iscsi_name, iscsi_target,
 
381
                                        0, volume_path, chap_auth,
 
382
                                        check_exit_code=False,
 
383
                                        old_name=old_name)
 
384
 
 
385
    def _fix_id_migration(self, context, volume):
 
386
        """Fix provider_location and dev files to address bug 1065702.
 
387
 
 
388
        For volumes that the provider_location has NOT been updated
 
389
        and are not currently in-use we'll create a new iscsi target
 
390
        and remove the persist file.
 
391
 
 
392
        If the volume is in-use, we'll just stick with the old name
 
393
        and when detach is called we'll feed back into ensure_export
 
394
        again if necessary and fix things up then.
 
395
 
 
396
        Details at: https://bugs.launchpad.net/cinder/+bug/1065702
 
397
        """
 
398
 
 
399
        model_update = {}
 
400
        pattern = re.compile(r":|\s")
 
401
        fields = pattern.split(volume['provider_location'])
 
402
        old_name = fields[3]
 
403
 
 
404
        volume['provider_location'] = \
 
405
            volume['provider_location'].replace(old_name, volume['name'])
 
406
        model_update['provider_location'] = volume['provider_location']
 
407
 
 
408
        self.db.volume_update(context, volume['id'], model_update)
 
409
 
 
410
        start = os.getcwd()
 
411
        os.chdir('/dev/%s' % self.configuration.volume_group)
 
412
 
 
413
        try:
 
414
            (out, err) = self._execute('readlink', old_name)
 
415
        except exception.ProcessExecutionError:
 
416
            link_path = '/dev/%s/%s' % (self.configuration.volume_group,
 
417
                                        old_name)
 
418
            LOG.debug(_('Symbolic link %s not found') % link_path)
 
419
            os.chdir(start)
 
420
            return
 
421
 
 
422
        rel_path = out.rstrip()
 
423
        self._execute('ln',
 
424
                      '-s',
 
425
                      rel_path, volume['name'],
 
426
                      run_as_root=True)
 
427
        os.chdir(start)
 
428
        return old_name
 
429
 
 
430
    def _ensure_iscsi_targets(self, context, host):
 
431
        """Ensure that target ids have been created in datastore."""
 
432
        # NOTE(jdg): tgtadm doesn't use the iscsi_targets table
 
433
        # TODO(jdg): In the future move all of the dependent stuff into the
 
434
        # cooresponding target admin class
 
435
        if not isinstance(self.tgtadm, iscsi.TgtAdm):
 
436
            host_iscsi_targets = self.db.iscsi_target_count_by_host(context,
 
437
                                                                    host)
 
438
            if host_iscsi_targets >= self.configuration.iscsi_num_targets:
 
439
                return
 
440
 
 
441
            # NOTE(vish): Target ids start at 1, not 0.
 
442
            target_end = self.configuration.iscsi_num_targets + 1
 
443
            for target_num in xrange(1, target_end):
 
444
                target = {'host': host, 'target_num': target_num}
 
445
                self.db.iscsi_target_create_safe(context, target)
 
446
 
 
447
    def create_export(self, context, volume):
 
448
        """Creates an export for a logical volume."""
 
449
 
 
450
        iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
 
451
                               volume['name'])
 
452
        volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
 
453
                                      volume['name'])
 
454
        model_update = {}
 
455
 
 
456
        # TODO(jdg): In the future move all of the dependent stuff into the
 
457
        # cooresponding target admin class
 
458
        if not isinstance(self.tgtadm, iscsi.TgtAdm):
 
459
            lun = 0
 
460
            self._ensure_iscsi_targets(context, volume['host'])
 
461
            iscsi_target = self.db.volume_allocate_iscsi_target(context,
 
462
                                                                volume['id'],
 
463
                                                                volume['host'])
 
464
        else:
 
465
            lun = 1  # For tgtadm the controller is lun 0, dev starts at lun 1
 
466
            iscsi_target = 0  # NOTE(jdg): Not used by tgtadm
 
467
 
 
468
        # Use the same method to generate the username and the password.
 
469
        chap_username = utils.generate_username()
 
470
        chap_password = utils.generate_password()
 
471
        chap_auth = self._iscsi_authentication('IncomingUser', chap_username,
 
472
                                               chap_password)
 
473
        # NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
 
474
        # should clean this all up at some point in the future
 
475
        tid = self.tgtadm.create_iscsi_target(iscsi_name,
 
476
                                              iscsi_target,
 
477
                                              0,
 
478
                                              volume_path,
 
479
                                              chap_auth)
 
480
        model_update['provider_location'] = self._iscsi_location(
 
481
            self.configuration.iscsi_ip_address, tid, iscsi_name, lun)
 
482
        model_update['provider_auth'] = self._iscsi_authentication(
 
483
            'CHAP', chap_username, chap_password)
 
484
        return model_update
 
485
 
 
486
    def remove_export(self, context, volume):
 
487
        """Removes an export for a logical volume."""
 
488
        # NOTE(jdg): tgtadm doesn't use the iscsi_targets table
 
489
        # TODO(jdg): In the future move all of the dependent stuff into the
 
490
        # cooresponding target admin class
 
491
 
 
492
        if isinstance(self.tgtadm, iscsi.LioAdm):
 
493
            try:
 
494
                iscsi_target = self.db.volume_get_iscsi_target_num(
 
495
                    context,
 
496
                    volume['id'])
 
497
            except exception.NotFound:
 
498
                LOG.info(_("Skipping remove_export. No iscsi_target "
 
499
                           "provisioned for volume: %s"), volume['id'])
 
500
                return
 
501
 
 
502
            self.tgtadm.remove_iscsi_target(iscsi_target, 0, volume['id'])
 
503
 
 
504
            return
 
505
 
 
506
        elif not isinstance(self.tgtadm, iscsi.TgtAdm):
 
507
            try:
 
508
                iscsi_target = self.db.volume_get_iscsi_target_num(
 
509
                    context,
 
510
                    volume['id'])
 
511
            except exception.NotFound:
 
512
                LOG.info(_("Skipping remove_export. No iscsi_target "
 
513
                           "provisioned for volume: %s"), volume['id'])
 
514
                return
 
515
        else:
 
516
            iscsi_target = 0
 
517
 
 
518
        try:
 
519
 
 
520
            # NOTE: provider_location may be unset if the volume hasn't
 
521
            # been exported
 
522
            location = volume['provider_location'].split(' ')
 
523
            iqn = location[1]
 
524
 
 
525
            # ietadm show will exit with an error
 
526
            # this export has already been removed
 
527
            self.tgtadm.show_target(iscsi_target, iqn=iqn)
 
528
 
 
529
        except Exception as e:
 
530
            LOG.info(_("Skipping remove_export. No iscsi_target "
 
531
                       "is presently exported for volume: %s"), volume['id'])
 
532
            return
 
533
 
 
534
        self.tgtadm.remove_iscsi_target(iscsi_target, 0, volume['id'])
 
535
 
 
536
    def get_volume_stats(self, refresh=False):
 
537
        """Get volume status.
 
538
 
 
539
        If 'refresh' is True, run update the stats first."""
 
540
        if refresh:
 
541
            self._update_volume_status()
 
542
 
 
543
        return self._stats
 
544
 
 
545
    def _update_volume_status(self):
 
546
        """Retrieve status info from volume group."""
 
547
 
 
548
        LOG.debug(_("Updating volume status"))
 
549
        data = {}
 
550
 
 
551
        # Note(zhiteng): These information are driver/backend specific,
 
552
        # each driver may define these values in its own config options
 
553
        # or fetch from driver specific configuration file.
 
554
        backend_name = self.configuration.safe_get('volume_backend_name')
 
555
        data["volume_backend_name"] = backend_name or 'LVM_iSCSI'
 
556
        data["vendor_name"] = 'Open Source'
 
557
        data["driver_version"] = '1.0'
 
558
        data["storage_protocol"] = 'iSCSI'
 
559
 
 
560
        data['total_capacity_gb'] = 0
 
561
        data['free_capacity_gb'] = 0
 
562
        data['reserved_percentage'] = self.configuration.reserved_percentage
 
563
        data['QoS_support'] = False
 
564
 
 
565
        try:
 
566
            out, err = self._execute('vgs', '--noheadings', '--nosuffix',
 
567
                                     '--unit=G', '-o', 'name,size,free',
 
568
                                     self.configuration.volume_group,
 
569
                                     run_as_root=True)
 
570
        except exception.ProcessExecutionError as exc:
 
571
            LOG.error(_("Error retrieving volume status: "), exc.stderr)
 
572
            out = False
 
573
 
 
574
        if out:
 
575
            volume = out.split()
 
576
            data['total_capacity_gb'] = float(volume[1])
 
577
            data['free_capacity_gb'] = float(volume[2])
 
578
 
 
579
        self._stats = data
 
580
 
 
581
    def _iscsi_location(self, ip, target, iqn, lun=None):
 
582
        return "%s:%s,%s %s %s" % (ip, self.configuration.iscsi_port,
 
583
                                   target, iqn, lun)
 
584
 
 
585
    def _iscsi_authentication(self, chap, name, password):
 
586
        return "%s %s %s" % (chap, name, password)
 
587
 
 
588
 
 
589
class ThinLVMVolumeDriver(LVMISCSIDriver):
 
590
    """Subclass for thin provisioned LVM's."""
 
591
    def __init__(self, *args, **kwargs):
 
592
        super(ThinLVMVolumeDriver, self).__init__(*args, **kwargs)
 
593
 
 
594
    def check_for_setup_error(self):
 
595
        """Returns an error if prerequisites aren't met"""
 
596
        out, err = self._execute('lvs', '--option',
 
597
                                 'name', '--noheadings',
 
598
                                 run_as_root=True)
 
599
        pool_name = "%s-pool" % FLAGS.volume_group
 
600
        if pool_name not in out:
 
601
            if not FLAGS.pool_size:
 
602
                out, err = self._execute('vgs', FLAGS.volume_group,
 
603
                                         '--noheadings', '--options',
 
604
                                         'name,size', run_as_root=True)
 
605
                size = re.sub(r'[\.][\d][\d]', '', out.split()[1])
 
606
            else:
 
607
                size = "%s" % FLAGS.pool_size
 
608
 
 
609
            pool_path = '%s/%s' % (FLAGS.volume_group, pool_name)
 
610
            out, err = self._execute('lvcreate', '-T', '-L', size,
 
611
                                     pool_path, run_as_root=True)
 
612
 
 
613
    def _do_lvm_snapshot(self, src_lvm_name, dest_vref, is_cinder_snap=True):
 
614
            if is_cinder_snap:
 
615
                new_name = self._escape_snapshot(dest_vref['name'])
 
616
            else:
 
617
                new_name = dest_vref['name']
 
618
 
 
619
            self._try_execute('lvcreate', '-s', '-n', new_name,
 
620
                              src_lvm_name, run_as_root=True)
 
621
 
 
622
    def create_volume(self, volume):
 
623
        """Creates a logical volume. Can optionally return a Dictionary of
 
624
        changes to the volume object to be persisted."""
 
625
        sizestr = self._sizestr(volume['size'])
 
626
        vg_name = ("%s/%s-pool" % (FLAGS.volume_group, FLAGS.volume_group))
 
627
        self._try_execute('lvcreate', '-T', '-V', sizestr, '-n',
 
628
                          volume['name'], vg_name, run_as_root=True)
 
629
 
 
630
    def delete_volume(self, volume):
 
631
        """Deletes a logical volume."""
 
632
        if self._volume_not_present(volume['name']):
 
633
            return True
 
634
        self._try_execute('lvremove', '-f', "%s/%s" %
 
635
                          (FLAGS.volume_group,
 
636
                           self._escape_snapshot(volume['name'])),
 
637
                          run_as_root=True)
 
638
 
 
639
    def create_cloned_volume(self, volume, src_vref):
 
640
        """Creates a clone of the specified volume."""
 
641
        LOG.info(_('Creating clone of volume: %s') % src_vref['id'])
 
642
        orig_lv_name = "%s/%s" % (FLAGS.volume_group, src_vref['name'])
 
643
        self._do_lvm_snapshot(orig_lv_name, volume, False)
 
644
 
 
645
    def create_snapshot(self, snapshot):
 
646
        """Creates a snapshot of a volume."""
 
647
        orig_lv_name = "%s/%s" % (FLAGS.volume_group, snapshot['volume_name'])
 
648
        self._do_lvm_snapshot(orig_lv_name, snapshot)