~ubuntu-cloud-archive/ubuntu/precise/nova/trunk

« 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, Adam Gandelman, Chuck Short
  • Date: 2012-04-12 14:14:29 UTC
  • Revision ID: package-import@ubuntu.com-20120412141429-dt69y6cd5e0uqbmk
Tags: 2012.1-0ubuntu2
[ Adam Gandelman ]
* debian/rules: Properly create empty doc/build/man dir for builds that
  skip doc building
* debian/control: Set 'Conflicts: nova-compute-hypervisor' for the various
  nova-compute-$type packages. (LP: #975616)
* debian/control: Set 'Breaks: nova-api' for the various nova-api-$service
  sub-packages. (LP: #966115)

[ Chuck Short ]
* Resynchronize with stable/essex:
  - b1d11b8 Use project_id in ec2.cloud._format_image()
  - 6e988ed Fixes image publication using deprecated auth. (LP: #977765)
  - 6e988ed Populate image properties with project_id again
  - 3b14c74 Fixed bug 962840, added a test case.
  - d4e96fe Allow unprivileged RADOS users to access rbd volumes.
  - 4acfab6 Stop libvirt test from deleting instances dir
  - 155c7b2 fix bug where nova ignores glance host in imageref
* debian/nova.conf: Enabled ec2_private_dns_show_ip so that juju can
  connect to openstack instances.
* debian/patches/fix-docs-build-without-network.patch: Fix docs build
  when there is no network access.

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)