~ubuntu-branches/ubuntu/saucy/nova/saucy-proposed

« back to all changes in this revision

Viewing changes to .pc/upstream/0003-Allow-unprivileged-RADOS-users-to-access-rbd-volumes.patch/nova/volume/driver.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-05-24 13:12:53 UTC
  • mfrom: (1.1.55)
  • Revision ID: package-import@ubuntu.com-20120524131253-ommql08fg1en06ut
Tags: 2012.2~f1-0ubuntu1
* New upstream release.
* Prepare for quantal:
  - Dropped debian/patches/upstream/0006-Use-project_id-in-ec2.cloud._format_image.patch
  - Dropped debian/patches/upstream/0005-Populate-image-properties-with-project_id-again.patch
  - Dropped debian/patches/upstream/0004-Fixed-bug-962840-added-a-test-case.patch
  - Dropped debian/patches/upstream/0003-Allow-unprivileged-RADOS-users-to-access-rbd-volumes.patch
  - Dropped debian/patches/upstream/0002-Stop-libvirt-test-from-deleting-instances-dir.patch
  - Dropped debian/patches/upstream/0001-fix-bug-where-nova-ignores-glance-host-in-imageref.patch 
  - Dropped debian/patches/0001-fix-useexisting-deprecation-warnings.patch
* debian/control: Add python-keystone as a dependency. (LP: #907197)
* debian/patches/kombu_tests_timeout.patch: Refreshed.
* debian/nova.conf, debian/nova-common.postinst: Convert to new ini
  file configuration
* debian/patches/nova-manage_flagfile_location.patch: Refreshed

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
 
Drivers for volumes.
20
 
 
21
 
"""
22
 
 
23
 
import time
24
 
 
25
 
from nova import exception
26
 
from nova import flags
27
 
from nova import log as logging
28
 
from nova.openstack.common import cfg
29
 
from nova import utils
30
 
from nova.volume import iscsi
31
 
 
32
 
 
33
 
LOG = logging.getLogger(__name__)
34
 
 
35
 
volume_opts = [
36
 
    cfg.StrOpt('volume_group',
37
 
               default='nova-volumes',
38
 
               help='Name for the VG that will contain exported volumes'),
39
 
    cfg.StrOpt('num_shell_tries',
40
 
               default=3,
41
 
               help='number of times to attempt to run flakey shell commands'),
42
 
    cfg.StrOpt('num_iscsi_scan_tries',
43
 
               default=3,
44
 
               help='number of times to rescan iSCSI target to find volume'),
45
 
    cfg.IntOpt('iscsi_num_targets',
46
 
               default=100,
47
 
               help='Number of iscsi target ids per host'),
48
 
    cfg.StrOpt('iscsi_target_prefix',
49
 
               default='iqn.2010-10.org.openstack:',
50
 
               help='prefix for iscsi volumes'),
51
 
    cfg.StrOpt('iscsi_ip_address',
52
 
               default='$my_ip',
53
 
               help='use this ip for iscsi'),
54
 
    cfg.IntOpt('iscsi_port',
55
 
               default=3260,
56
 
               help='The port that the iSCSI daemon is listening on'),
57
 
    cfg.StrOpt('rbd_pool',
58
 
               default='rbd',
59
 
               help='the rbd pool in which volumes are stored'),
60
 
    ]
61
 
 
62
 
FLAGS = flags.FLAGS
63
 
FLAGS.register_opts(volume_opts)
64
 
 
65
 
 
66
 
class VolumeDriver(object):
67
 
    """Executes commands relating to Volumes."""
68
 
    def __init__(self, execute=utils.execute, *args, **kwargs):
69
 
        # NOTE(vish): db is set by Manager
70
 
        self.db = None
71
 
        self.set_execute(execute)
72
 
 
73
 
    def set_execute(self, execute):
74
 
        self._execute = execute
75
 
 
76
 
    def _try_execute(self, *command, **kwargs):
77
 
        # NOTE(vish): Volume commands can partially fail due to timing, but
78
 
        #             running them a second time on failure will usually
79
 
        #             recover nicely.
80
 
        tries = 0
81
 
        while True:
82
 
            try:
83
 
                self._execute(*command, **kwargs)
84
 
                return True
85
 
            except exception.ProcessExecutionError:
86
 
                tries = tries + 1
87
 
                if tries >= FLAGS.num_shell_tries:
88
 
                    raise
89
 
                LOG.exception(_("Recovering from a failed execute.  "
90
 
                                "Try number %s"), tries)
91
 
                time.sleep(tries ** 2)
92
 
 
93
 
    def check_for_setup_error(self):
94
 
        """Returns an error if prerequisites aren't met"""
95
 
        out, err = self._execute('vgs', '--noheadings', '-o', 'name',
96
 
                                run_as_root=True)
97
 
        volume_groups = out.split()
98
 
        if not FLAGS.volume_group in volume_groups:
99
 
            raise exception.Error(_("volume group %s doesn't exist")
100
 
                                  % FLAGS.volume_group)
101
 
 
102
 
    def _create_volume(self, volume_name, sizestr):
103
 
        self._try_execute('lvcreate', '-L', sizestr, '-n',
104
 
                          volume_name, FLAGS.volume_group, run_as_root=True)
105
 
 
106
 
    def _copy_volume(self, srcstr, deststr, size_in_g):
107
 
        self._execute('dd', 'if=%s' % srcstr, 'of=%s' % deststr,
108
 
                      'count=%d' % (size_in_g * 1024), 'bs=1M',
109
 
                      run_as_root=True)
110
 
 
111
 
    def _volume_not_present(self, volume_name):
112
 
        path_name = '%s/%s' % (FLAGS.volume_group, volume_name)
113
 
        try:
114
 
            self._try_execute('lvdisplay', path_name, run_as_root=True)
115
 
        except Exception as e:
116
 
            # If the volume isn't present
117
 
            return True
118
 
        return False
119
 
 
120
 
    def _delete_volume(self, volume, size_in_g):
121
 
        """Deletes a logical volume."""
122
 
        # zero out old volumes to prevent data leaking between users
123
 
        # TODO(ja): reclaiming space should be done lazy and low priority
124
 
        self._copy_volume('/dev/zero', self.local_path(volume), size_in_g)
125
 
        self._try_execute('lvremove', '-f', "%s/%s" %
126
 
                          (FLAGS.volume_group,
127
 
                           self._escape_snapshot(volume['name'])),
128
 
                          run_as_root=True)
129
 
 
130
 
    def _sizestr(self, size_in_g):
131
 
        if int(size_in_g) == 0:
132
 
            return '100M'
133
 
        return '%sG' % size_in_g
134
 
 
135
 
    # Linux LVM reserves name that starts with snapshot, so that
136
 
    # such volume name can't be created. Mangle it.
137
 
    def _escape_snapshot(self, snapshot_name):
138
 
        if not snapshot_name.startswith('snapshot'):
139
 
            return snapshot_name
140
 
        return '_' + snapshot_name
141
 
 
142
 
    def create_volume(self, volume):
143
 
        """Creates a logical volume. Can optionally return a Dictionary of
144
 
        changes to the volume object to be persisted."""
145
 
        self._create_volume(volume['name'], self._sizestr(volume['size']))
146
 
 
147
 
    def create_volume_from_snapshot(self, volume, snapshot):
148
 
        """Creates a volume from a snapshot."""
149
 
        self._create_volume(volume['name'], self._sizestr(volume['size']))
150
 
        self._copy_volume(self.local_path(snapshot), self.local_path(volume),
151
 
                          snapshot['volume_size'])
152
 
 
153
 
    def delete_volume(self, volume):
154
 
        """Deletes a logical volume."""
155
 
        if self._volume_not_present(volume['name']):
156
 
            # If the volume isn't present, then don't attempt to delete
157
 
            return True
158
 
 
159
 
        # TODO(yamahata): lvm can't delete origin volume only without
160
 
        # deleting derived snapshots. Can we do something fancy?
161
 
        out, err = self._execute('lvdisplay', '--noheading',
162
 
                                 '-C', '-o', 'Attr',
163
 
                                 '%s/%s' % (FLAGS.volume_group,
164
 
                                            volume['name']),
165
 
                                 run_as_root=True)
166
 
        # fake_execute returns None resulting unit test error
167
 
        if out:
168
 
            out = out.strip()
169
 
            if (out[0] == 'o') or (out[0] == 'O'):
170
 
                raise exception.VolumeIsBusy(volume_name=volume['name'])
171
 
 
172
 
        self._delete_volume(volume, volume['size'])
173
 
 
174
 
    def create_snapshot(self, snapshot):
175
 
        """Creates a snapshot."""
176
 
        orig_lv_name = "%s/%s" % (FLAGS.volume_group, snapshot['volume_name'])
177
 
        self._try_execute('lvcreate', '-L',
178
 
                          self._sizestr(snapshot['volume_size']),
179
 
                          '--name', self._escape_snapshot(snapshot['name']),
180
 
                          '--snapshot', orig_lv_name, run_as_root=True)
181
 
 
182
 
    def delete_snapshot(self, snapshot):
183
 
        """Deletes a snapshot."""
184
 
        if self._volume_not_present(self._escape_snapshot(snapshot['name'])):
185
 
            # If the snapshot isn't present, then don't attempt to delete
186
 
            return True
187
 
 
188
 
        # TODO(yamahata): zeroing out the whole snapshot triggers COW.
189
 
        # it's quite slow.
190
 
        self._delete_volume(snapshot, snapshot['volume_size'])
191
 
 
192
 
    def local_path(self, volume):
193
 
        # NOTE(vish): stops deprecation warning
194
 
        escaped_group = FLAGS.volume_group.replace('-', '--')
195
 
        escaped_name = self._escape_snapshot(volume['name']).replace('-', '--')
196
 
        return "/dev/mapper/%s-%s" % (escaped_group, escaped_name)
197
 
 
198
 
    def ensure_export(self, context, volume):
199
 
        """Synchronously recreates an export for a logical volume."""
200
 
        raise NotImplementedError()
201
 
 
202
 
    def create_export(self, context, volume):
203
 
        """Exports the volume. Can optionally return a Dictionary of changes
204
 
        to the volume object to be persisted."""
205
 
        raise NotImplementedError()
206
 
 
207
 
    def remove_export(self, context, volume):
208
 
        """Removes an export for a logical volume."""
209
 
        raise NotImplementedError()
210
 
 
211
 
    def check_for_export(self, context, volume_id):
212
 
        """Make sure volume is exported."""
213
 
        raise NotImplementedError()
214
 
 
215
 
    def initialize_connection(self, volume, connector):
216
 
        """Allow connection to connector and return connection info."""
217
 
        raise NotImplementedError()
218
 
 
219
 
    def terminate_connection(self, volume, connector):
220
 
        """Disallow connection from connector"""
221
 
        raise NotImplementedError()
222
 
 
223
 
    def get_volume_stats(self, refresh=False):
224
 
        """Return the current state of the volume service. If 'refresh' is
225
 
           True, run the update first."""
226
 
        return None
227
 
 
228
 
    def do_setup(self, context):
229
 
        """Any initialization the volume driver does while starting"""
230
 
        pass
231
 
 
232
 
 
233
 
class ISCSIDriver(VolumeDriver):
234
 
    """Executes commands relating to ISCSI volumes.
235
 
 
236
 
    We make use of model provider properties as follows:
237
 
 
238
 
    ``provider_location``
239
 
      if present, contains the iSCSI target information in the same
240
 
      format as an ietadm discovery
241
 
      i.e. '<ip>:<port>,<portal> <target IQN>'
242
 
 
243
 
    ``provider_auth``
244
 
      if present, contains a space-separated triple:
245
 
      '<auth method> <auth username> <auth password>'.
246
 
      `CHAP` is the only auth_method in use at the moment.
247
 
    """
248
 
 
249
 
    def __init__(self, *args, **kwargs):
250
 
        self.tgtadm = iscsi.get_target_admin()
251
 
        super(ISCSIDriver, self).__init__(*args, **kwargs)
252
 
 
253
 
    def set_execute(self, execute):
254
 
        super(ISCSIDriver, self).set_execute(execute)
255
 
        self.tgtadm.set_execute(execute)
256
 
 
257
 
    def ensure_export(self, context, volume):
258
 
        """Synchronously recreates an export for a logical volume."""
259
 
        try:
260
 
            iscsi_target = self.db.volume_get_iscsi_target_num(context,
261
 
                                                           volume['id'])
262
 
        except exception.NotFound:
263
 
            LOG.info(_("Skipping ensure_export. No iscsi_target " +
264
 
                       "provisioned for volume: %d"), volume['id'])
265
 
            return
266
 
 
267
 
        iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
268
 
        volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
269
 
 
270
 
        self.tgtadm.new_target(iscsi_name, iscsi_target, check_exit_code=False)
271
 
        self.tgtadm.new_logicalunit(iscsi_target, 0, volume_path,
272
 
                                    check_exit_code=False)
273
 
 
274
 
    def _ensure_iscsi_targets(self, context, host):
275
 
        """Ensure that target ids have been created in datastore."""
276
 
        host_iscsi_targets = self.db.iscsi_target_count_by_host(context, host)
277
 
        if host_iscsi_targets >= FLAGS.iscsi_num_targets:
278
 
            return
279
 
        # NOTE(vish): Target ids start at 1, not 0.
280
 
        for target_num in xrange(1, FLAGS.iscsi_num_targets + 1):
281
 
            target = {'host': host, 'target_num': target_num}
282
 
            self.db.iscsi_target_create_safe(context, target)
283
 
 
284
 
    def create_export(self, context, volume):
285
 
        """Creates an export for a logical volume."""
286
 
        self._ensure_iscsi_targets(context, volume['host'])
287
 
        iscsi_target = self.db.volume_allocate_iscsi_target(context,
288
 
                                                      volume['id'],
289
 
                                                      volume['host'])
290
 
        iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
291
 
        volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
292
 
 
293
 
        self.tgtadm.new_target(iscsi_name, iscsi_target)
294
 
        self.tgtadm.new_logicalunit(iscsi_target, 0, volume_path)
295
 
 
296
 
        model_update = {}
297
 
        if FLAGS.iscsi_helper == 'tgtadm':
298
 
            lun = 1
299
 
        else:
300
 
            lun = 0
301
 
        model_update['provider_location'] = _iscsi_location(
302
 
            FLAGS.iscsi_ip_address, iscsi_target, iscsi_name, lun)
303
 
        return model_update
304
 
 
305
 
    def remove_export(self, context, volume):
306
 
        """Removes an export for a logical volume."""
307
 
        try:
308
 
            iscsi_target = self.db.volume_get_iscsi_target_num(context,
309
 
                                                           volume['id'])
310
 
        except exception.NotFound:
311
 
            LOG.info(_("Skipping remove_export. No iscsi_target " +
312
 
                       "provisioned for volume: %d"), volume['id'])
313
 
            return
314
 
 
315
 
        try:
316
 
            # ietadm show will exit with an error
317
 
            # this export has already been removed
318
 
            self.tgtadm.show_target(iscsi_target)
319
 
        except Exception as e:
320
 
            LOG.info(_("Skipping remove_export. No iscsi_target " +
321
 
                       "is presently exported for volume: %d"), volume['id'])
322
 
            return
323
 
 
324
 
        self.tgtadm.delete_logicalunit(iscsi_target, 0)
325
 
        self.tgtadm.delete_target(iscsi_target)
326
 
 
327
 
    def _do_iscsi_discovery(self, volume):
328
 
        #TODO(justinsb): Deprecate discovery and use stored info
329
 
        #NOTE(justinsb): Discovery won't work with CHAP-secured targets (?)
330
 
        LOG.warn(_("ISCSI provider_location not stored, using discovery"))
331
 
 
332
 
        volume_name = volume['name']
333
 
 
334
 
        (out, _err) = self._execute('iscsiadm', '-m', 'discovery',
335
 
                                    '-t', 'sendtargets', '-p', volume['host'],
336
 
                                    run_as_root=True)
337
 
        for target in out.splitlines():
338
 
            if FLAGS.iscsi_ip_address in target and volume_name in target:
339
 
                return target
340
 
        return None
341
 
 
342
 
    def _get_iscsi_properties(self, volume):
343
 
        """Gets iscsi configuration
344
 
 
345
 
        We ideally get saved information in the volume entity, but fall back
346
 
        to discovery if need be. Discovery may be completely removed in future
347
 
        The properties are:
348
 
 
349
 
        :target_discovered:    boolean indicating whether discovery was used
350
 
 
351
 
        :target_iqn:    the IQN of the iSCSI target
352
 
 
353
 
        :target_portal:    the portal of the iSCSI target
354
 
 
355
 
        :target_lun:    the lun of the iSCSI target
356
 
 
357
 
        :volume_id:    the id of the volume (currently used by xen)
358
 
 
359
 
        :auth_method:, :auth_username:, :auth_password:
360
 
 
361
 
            the authentication details. Right now, either auth_method is not
362
 
            present meaning no authentication, or auth_method == `CHAP`
363
 
            meaning use CHAP with the specified credentials.
364
 
        """
365
 
 
366
 
        properties = {}
367
 
 
368
 
        location = volume['provider_location']
369
 
 
370
 
        if location:
371
 
            # provider_location is the same format as iSCSI discovery output
372
 
            properties['target_discovered'] = False
373
 
        else:
374
 
            location = self._do_iscsi_discovery(volume)
375
 
 
376
 
            if not location:
377
 
                raise exception.Error(_("Could not find iSCSI export "
378
 
                                        " for volume %s") %
379
 
                                      (volume['name']))
380
 
 
381
 
            LOG.debug(_("ISCSI Discovery: Found %s") % (location))
382
 
            properties['target_discovered'] = True
383
 
 
384
 
        results = location.split(" ")
385
 
        properties['target_portal'] = results[0].split(",")[0]
386
 
        properties['target_iqn'] = results[1]
387
 
        try:
388
 
            properties['target_lun'] = int(results[2])
389
 
        except (IndexError, ValueError):
390
 
            if FLAGS.iscsi_helper == 'tgtadm':
391
 
                properties['target_lun'] = 1
392
 
            else:
393
 
                properties['target_lun'] = 0
394
 
 
395
 
        properties['volume_id'] = volume['id']
396
 
 
397
 
        auth = volume['provider_auth']
398
 
        if auth:
399
 
            (auth_method, auth_username, auth_secret) = auth.split()
400
 
 
401
 
            properties['auth_method'] = auth_method
402
 
            properties['auth_username'] = auth_username
403
 
            properties['auth_password'] = auth_secret
404
 
 
405
 
        return properties
406
 
 
407
 
    def _run_iscsiadm(self, iscsi_properties, iscsi_command):
408
 
        (out, err) = self._execute('iscsiadm', '-m', 'node', '-T',
409
 
                                   iscsi_properties['target_iqn'],
410
 
                                   '-p', iscsi_properties['target_portal'],
411
 
                                   *iscsi_command, run_as_root=True)
412
 
        LOG.debug("iscsiadm %s: stdout=%s stderr=%s" %
413
 
                  (iscsi_command, out, err))
414
 
        return (out, err)
415
 
 
416
 
    def _iscsiadm_update(self, iscsi_properties, property_key, property_value):
417
 
        iscsi_command = ('--op', 'update', '-n', property_key,
418
 
                         '-v', property_value)
419
 
        return self._run_iscsiadm(iscsi_properties, iscsi_command)
420
 
 
421
 
    def initialize_connection(self, volume, connector):
422
 
        """Initializes the connection and returns connection info.
423
 
 
424
 
        The iscsi driver returns a driver_volume_type of 'iscsi'.
425
 
        The format of the driver data is defined in _get_iscsi_properties.
426
 
        Example return value::
427
 
 
428
 
            {
429
 
                'driver_volume_type': 'iscsi'
430
 
                'data': {
431
 
                    'target_discovered': True,
432
 
                    'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
433
 
                    'target_portal': '127.0.0.0.1:3260',
434
 
                    'volume_id': 1,
435
 
                }
436
 
            }
437
 
 
438
 
        """
439
 
 
440
 
        iscsi_properties = self._get_iscsi_properties(volume)
441
 
        return {
442
 
            'driver_volume_type': 'iscsi',
443
 
            'data': iscsi_properties
444
 
        }
445
 
 
446
 
    def terminate_connection(self, volume, connector):
447
 
        pass
448
 
 
449
 
    def check_for_export(self, context, volume_id):
450
 
        """Make sure volume is exported."""
451
 
 
452
 
        tid = self.db.volume_get_iscsi_target_num(context, volume_id)
453
 
        try:
454
 
            self.tgtadm.show_target(tid)
455
 
        except exception.ProcessExecutionError, e:
456
 
            # Instances remount read-only in this case.
457
 
            # /etc/init.d/iscsitarget restart and rebooting nova-volume
458
 
            # is better since ensure_export() works at boot time.
459
 
            LOG.error(_("Cannot confirm exported volume "
460
 
                        "id:%(volume_id)s.") % locals())
461
 
            raise
462
 
 
463
 
 
464
 
class FakeISCSIDriver(ISCSIDriver):
465
 
    """Logs calls instead of executing."""
466
 
    def __init__(self, *args, **kwargs):
467
 
        super(FakeISCSIDriver, self).__init__(execute=self.fake_execute,
468
 
                                              *args, **kwargs)
469
 
 
470
 
    def check_for_setup_error(self):
471
 
        """No setup necessary in fake mode."""
472
 
        pass
473
 
 
474
 
    def initialize_connection(self, volume, connector):
475
 
        return {
476
 
            'driver_volume_type': 'iscsi',
477
 
            'data': {}
478
 
        }
479
 
 
480
 
    def terminate_connection(self, volume, connector):
481
 
        pass
482
 
 
483
 
    @staticmethod
484
 
    def fake_execute(cmd, *_args, **_kwargs):
485
 
        """Execute that simply logs the command."""
486
 
        LOG.debug(_("FAKE ISCSI: %s"), cmd)
487
 
        return (None, None)
488
 
 
489
 
 
490
 
class RBDDriver(VolumeDriver):
491
 
    """Implements RADOS block device (RBD) volume commands"""
492
 
 
493
 
    def check_for_setup_error(self):
494
 
        """Returns an error if prerequisites aren't met"""
495
 
        (stdout, stderr) = self._execute('rados', 'lspools')
496
 
        pools = stdout.split("\n")
497
 
        if not FLAGS.rbd_pool in pools:
498
 
            raise exception.Error(_("rbd has no pool %s") %
499
 
                                  FLAGS.rbd_pool)
500
 
 
501
 
    def create_volume(self, volume):
502
 
        """Creates a logical volume."""
503
 
        if int(volume['size']) == 0:
504
 
            size = 100
505
 
        else:
506
 
            size = int(volume['size']) * 1024
507
 
        self._try_execute('rbd', '--pool', FLAGS.rbd_pool,
508
 
                          '--size', size, 'create', volume['name'])
509
 
 
510
 
    def delete_volume(self, volume):
511
 
        """Deletes a logical volume."""
512
 
        self._try_execute('rbd', '--pool', FLAGS.rbd_pool,
513
 
                          'rm', volume['name'])
514
 
 
515
 
    def create_snapshot(self, snapshot):
516
 
        """Creates an rbd snapshot"""
517
 
        self._try_execute('rbd', '--pool', FLAGS.rbd_pool,
518
 
                          'snap', 'create', '--snap', snapshot['name'],
519
 
                          snapshot['volume_name'])
520
 
 
521
 
    def delete_snapshot(self, snapshot):
522
 
        """Deletes an rbd snapshot"""
523
 
        self._try_execute('rbd', '--pool', FLAGS.rbd_pool,
524
 
                          'snap', 'rm', '--snap', snapshot['name'],
525
 
                          snapshot['volume_name'])
526
 
 
527
 
    def local_path(self, volume):
528
 
        """Returns the path of the rbd volume."""
529
 
        # This is the same as the remote path
530
 
        # since qemu accesses it directly.
531
 
        return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
532
 
 
533
 
    def ensure_export(self, context, volume):
534
 
        """Synchronously recreates an export for a logical volume."""
535
 
        pass
536
 
 
537
 
    def create_export(self, context, volume):
538
 
        """Exports the volume"""
539
 
        pass
540
 
 
541
 
    def remove_export(self, context, volume):
542
 
        """Removes an export for a logical volume"""
543
 
        pass
544
 
 
545
 
    def initialize_connection(self, volume, connector):
546
 
        return {
547
 
            'driver_volume_type': 'rbd',
548
 
            'data': {
549
 
                'name': '%s/%s' % (FLAGS.rbd_pool, volume['name'])
550
 
            }
551
 
        }
552
 
 
553
 
    def terminate_connection(self, volume, connector):
554
 
        pass
555
 
 
556
 
 
557
 
class SheepdogDriver(VolumeDriver):
558
 
    """Executes commands relating to Sheepdog Volumes"""
559
 
 
560
 
    def check_for_setup_error(self):
561
 
        """Returns an error if prerequisites aren't met"""
562
 
        try:
563
 
            #NOTE(francois-charlier) Since 0.24 'collie cluster info -r'
564
 
            #  gives short output, but for compatibility reason we won't
565
 
            #  use it and just check if 'running' is in the output.
566
 
            (out, err) = self._execute('collie', 'cluster', 'info')
567
 
            if not 'running' in out.split():
568
 
                raise exception.Error(_("Sheepdog is not working: %s") % out)
569
 
        except exception.ProcessExecutionError:
570
 
            raise exception.Error(_("Sheepdog is not working"))
571
 
 
572
 
    def create_volume(self, volume):
573
 
        """Creates a sheepdog volume"""
574
 
        self._try_execute('qemu-img', 'create',
575
 
                          "sheepdog:%s" % volume['name'],
576
 
                          self._sizestr(volume['size']))
577
 
 
578
 
    def create_volume_from_snapshot(self, volume, snapshot):
579
 
        """Creates a sheepdog volume from a snapshot."""
580
 
        self._try_execute('qemu-img', 'create', '-b',
581
 
                          "sheepdog:%s:%s" % (snapshot['volume_name'],
582
 
                                              snapshot['name']),
583
 
                          "sheepdog:%s" % volume['name'])
584
 
 
585
 
    def delete_volume(self, volume):
586
 
        """Deletes a logical volume"""
587
 
        self._try_execute('collie', 'vdi', 'delete', volume['name'])
588
 
 
589
 
    def create_snapshot(self, snapshot):
590
 
        """Creates a sheepdog snapshot"""
591
 
        self._try_execute('qemu-img', 'snapshot', '-c', snapshot['name'],
592
 
                          "sheepdog:%s" % snapshot['volume_name'])
593
 
 
594
 
    def delete_snapshot(self, snapshot):
595
 
        """Deletes a sheepdog snapshot"""
596
 
        self._try_execute('collie', 'vdi', 'delete', snapshot['volume_name'],
597
 
                          '-s', snapshot['name'])
598
 
 
599
 
    def local_path(self, volume):
600
 
        return "sheepdog:%s" % volume['name']
601
 
 
602
 
    def ensure_export(self, context, volume):
603
 
        """Safely and synchronously recreates an export for a logical volume"""
604
 
        pass
605
 
 
606
 
    def create_export(self, context, volume):
607
 
        """Exports the volume"""
608
 
        pass
609
 
 
610
 
    def remove_export(self, context, volume):
611
 
        """Removes an export for a logical volume"""
612
 
        pass
613
 
 
614
 
    def initialize_connection(self, volume, connector):
615
 
        return {
616
 
            'driver_volume_type': 'sheepdog',
617
 
            'data': {
618
 
                'name': volume['name']
619
 
            }
620
 
        }
621
 
 
622
 
    def terminate_connection(self, volume, connector):
623
 
        pass
624
 
 
625
 
 
626
 
class LoggingVolumeDriver(VolumeDriver):
627
 
    """Logs and records calls, for unit tests."""
628
 
 
629
 
    def check_for_setup_error(self):
630
 
        pass
631
 
 
632
 
    def create_volume(self, volume):
633
 
        self.log_action('create_volume', volume)
634
 
 
635
 
    def delete_volume(self, volume):
636
 
        self.log_action('delete_volume', volume)
637
 
 
638
 
    def local_path(self, volume):
639
 
        print "local_path not implemented"
640
 
        raise NotImplementedError()
641
 
 
642
 
    def ensure_export(self, context, volume):
643
 
        self.log_action('ensure_export', volume)
644
 
 
645
 
    def create_export(self, context, volume):
646
 
        self.log_action('create_export', volume)
647
 
 
648
 
    def remove_export(self, context, volume):
649
 
        self.log_action('remove_export', volume)
650
 
 
651
 
    def initialize_connection(self, volume, connector):
652
 
        self.log_action('initialize_connection', volume)
653
 
 
654
 
    def terminate_connection(self, volume, connector):
655
 
        self.log_action('terminate_connection', volume)
656
 
 
657
 
    def check_for_export(self, context, volume_id):
658
 
        self.log_action('check_for_export', volume_id)
659
 
 
660
 
    _LOGS = []
661
 
 
662
 
    @staticmethod
663
 
    def clear_logs():
664
 
        LoggingVolumeDriver._LOGS = []
665
 
 
666
 
    @staticmethod
667
 
    def log_action(action, parameters):
668
 
        """Logs the command."""
669
 
        LOG.debug(_("LoggingVolumeDriver: %s") % (action))
670
 
        log_dictionary = {}
671
 
        if parameters:
672
 
            log_dictionary = dict(parameters)
673
 
        log_dictionary['action'] = action
674
 
        LOG.debug(_("LoggingVolumeDriver: %s") % (log_dictionary))
675
 
        LoggingVolumeDriver._LOGS.append(log_dictionary)
676
 
 
677
 
    @staticmethod
678
 
    def all_logs():
679
 
        return LoggingVolumeDriver._LOGS
680
 
 
681
 
    @staticmethod
682
 
    def logs_like(action, **kwargs):
683
 
        matches = []
684
 
        for entry in LoggingVolumeDriver._LOGS:
685
 
            if entry['action'] != action:
686
 
                continue
687
 
            match = True
688
 
            for k, v in kwargs.iteritems():
689
 
                if entry.get(k) != v:
690
 
                    match = False
691
 
                    break
692
 
            if match:
693
 
                matches.append(entry)
694
 
        return matches
695
 
 
696
 
 
697
 
def _iscsi_location(ip, target, iqn, lun=None):
698
 
    return "%s:%s,%s %s %s" % (ip, FLAGS.iscsi_port, target, iqn, lun)