~ubuntu-branches/ubuntu/raring/cinder/raring-updates

« back to all changes in this revision

Viewing changes to cinder/volume/api.py

Tags: upstream-2013.1~g2
ImportĀ upstreamĀ versionĀ 2013.1~g2

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import functools
24
24
 
25
25
from cinder.db import base
 
26
from cinder.db.sqlalchemy import models
26
27
from cinder import exception
27
28
from cinder import flags
28
29
from cinder.image import glance
38
39
from cinder.volume import volume_types
39
40
 
40
41
volume_host_opt = cfg.BoolOpt('snapshot_same_host',
41
 
        default=True,
42
 
        help='Create volume from snapshot at the host where snapshot resides')
 
42
                              default=True,
 
43
                              help='Create volume from snapshot at the host '
 
44
                                   'where snapshot resides')
43
45
 
44
46
FLAGS = flags.FLAGS
45
47
FLAGS.register_opt(volume_host_opt)
85
87
        super(API, self).__init__(db_driver)
86
88
 
87
89
    def create(self, context, size, name, description, snapshot=None,
88
 
                image_id=None, volume_type=None, metadata=None,
89
 
                availability_zone=None):
 
90
               image_id=None, volume_type=None, metadata=None,
 
91
               availability_zone=None, source_volume=None):
 
92
 
 
93
        if ((snapshot is not None) and (source_volume is not None)):
 
94
            msg = (_("May specify either snapshot, "
 
95
                     "or src volume but not both!"))
 
96
            raise exception.InvalidInput(reason=msg)
 
97
 
90
98
        check_policy(context, 'create')
91
99
        if snapshot is not None:
92
100
            if snapshot['status'] != "available":
99
107
        else:
100
108
            snapshot_id = None
101
109
 
 
110
        if source_volume is not None:
 
111
            if source_volume['status'] == "error":
 
112
                msg = _("Unable to clone volumes that are in an error state")
 
113
                raise exception.InvalidSourceVolume(reason=msg)
 
114
            if not size:
 
115
                size = source_volume['size']
 
116
            else:
 
117
                if size < source_volume['size']:
 
118
                    msg = _("Clones currently must be "
 
119
                            ">= original volume size.")
 
120
                    raise exception.InvalidInput(reason=msg)
 
121
            source_volid = source_volume['id']
 
122
        else:
 
123
            source_volid = None
 
124
 
102
125
        def as_int(s):
103
126
            try:
104
127
                return int(s)
105
 
            except ValueError:
 
128
            except (ValueError, TypeError):
106
129
                return s
107
130
 
108
131
        # tolerate size as stringified int
113
136
                   % size)
114
137
            raise exception.InvalidInput(reason=msg)
115
138
 
116
 
        if image_id:
 
139
        if (image_id and not (source_volume or snapshot)):
117
140
            # check image existence
118
141
            image_meta = self.image_service.show(context, image_id)
119
142
            image_size_in_gb = (int(image_meta['size']) + GB - 1) / GB
143
166
            elif 'volumes' in overs:
144
167
                consumed = _consumed('volumes')
145
168
                LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
146
 
                           "volume (%(consumed)d volumes already consumed)")
147
 
                           % locals())
 
169
                           "volume (%(consumed)d volumes "
 
170
                           "already consumed)") % locals())
148
171
                raise exception.VolumeLimitExceeded(allowed=quotas['volumes'])
149
172
 
150
173
        if availability_zone is None:
151
174
            availability_zone = FLAGS.storage_availability_zone
152
175
 
153
 
        if not volume_type:
 
176
        if not volume_type and not source_volume:
154
177
            volume_type = volume_types.get_default_volume_type()
155
178
 
156
 
        volume_type_id = volume_type.get('id')
 
179
        if not volume_type and source_volume:
 
180
            volume_type_id = source_volume['volume_type_id']
 
181
        else:
 
182
            volume_type_id = volume_type.get('id')
157
183
 
158
 
        options = {
159
 
            'size': size,
160
 
            'user_id': context.user_id,
161
 
            'project_id': context.project_id,
162
 
            'snapshot_id': snapshot_id,
163
 
            'availability_zone': availability_zone,
164
 
            'status': "creating",
165
 
            'attach_status': "detached",
166
 
            'display_name': name,
167
 
            'display_description': description,
168
 
            'volume_type_id': volume_type_id,
169
 
            'metadata': metadata,
170
 
            }
 
184
        options = {'size': size,
 
185
                   'user_id': context.user_id,
 
186
                   'project_id': context.project_id,
 
187
                   'snapshot_id': snapshot_id,
 
188
                   'availability_zone': availability_zone,
 
189
                   'status': "creating",
 
190
                   'attach_status': "detached",
 
191
                   'display_name': name,
 
192
                   'display_description': description,
 
193
                   'volume_type_id': volume_type_id,
 
194
                   'metadata': metadata,
 
195
                   'source_volid': source_volid}
171
196
 
172
197
        try:
173
198
            volume = self.db.volume_create(context, options)
179
204
                finally:
180
205
                    QUOTAS.rollback(context, reservations)
181
206
 
182
 
        request_spec = {
183
 
            'volume_properties': options,
184
 
            'volume_type': volume_type,
185
 
            'volume_id': volume['id'],
186
 
            'snapshot_id': volume['snapshot_id'],
187
 
            'image_id': image_id
188
 
        }
 
207
        request_spec = {'volume_properties': options,
 
208
                        'volume_type': volume_type,
 
209
                        'volume_id': volume['id'],
 
210
                        'snapshot_id': volume['snapshot_id'],
 
211
                        'image_id': image_id,
 
212
                        'source_volid': volume['source_volid']}
189
213
 
190
214
        filter_properties = {}
191
215
 
199
223
        # If snapshot_id is set, make the call create volume directly to
200
224
        # the volume host where the snapshot resides instead of passing it
201
225
        # through the scheduler. So snapshot can be copy to new volume.
 
226
 
 
227
        source_volid = request_spec['source_volid']
202
228
        volume_id = request_spec['volume_id']
203
229
        snapshot_id = request_spec['snapshot_id']
204
230
        image_id = request_spec['image_id']
205
231
 
206
232
        if snapshot_id and FLAGS.snapshot_same_host:
207
233
            snapshot_ref = self.db.snapshot_get(context, snapshot_id)
208
 
            src_volume_ref = self.db.volume_get(context,
209
 
                                                snapshot_ref['volume_id'])
210
 
            volume_ref = self.db.volume_get(context,
211
 
                                            volume_id)
212
 
            # bypass scheduler and send request directly to volume
213
 
            self.volume_rpcapi.create_volume(context,
214
 
                                            volume_ref,
215
 
                                            src_volume_ref['host'],
216
 
                                            snapshot_id,
217
 
                                            image_id)
 
234
            source_volume_ref = self.db.volume_get(context,
 
235
                                                   snapshot_ref['volume_id'])
 
236
            now = timeutils.utcnow()
 
237
            values = {'host': source_volume_ref['host'], 'scheduled_at': now}
 
238
            volume_ref = self.db.volume_update(context, volume_id, values)
 
239
 
 
240
            # bypass scheduler and send request directly to volume
 
241
            self.volume_rpcapi.create_volume(context,
 
242
                                             volume_ref,
 
243
                                             volume_ref['host'],
 
244
                                             snapshot_id,
 
245
                                             image_id)
 
246
        elif source_volid:
 
247
            source_volume_ref = self.db.volume_get(context,
 
248
                                                   source_volid)
 
249
            now = timeutils.utcnow()
 
250
            values = {'host': source_volume_ref['host'], 'scheduled_at': now}
 
251
            volume_ref = self.db.volume_update(context, volume_id, values)
 
252
 
 
253
            # bypass scheduler and send request directly to volume
 
254
            self.volume_rpcapi.create_volume(context,
 
255
                                             volume_ref,
 
256
                                             volume_ref['host'],
 
257
                                             snapshot_id,
 
258
                                             image_id,
 
259
                                             source_volid)
218
260
        else:
219
 
            self.scheduler_rpcapi.create_volume(context,
220
 
                                    FLAGS.volume_topic,
221
 
                                    volume_id,
222
 
                                    snapshot_id,
223
 
                                    image_id,
224
 
                                    request_spec=request_spec,
225
 
                                    filter_properties=filter_properties)
 
261
            self.scheduler_rpcapi.create_volume(
 
262
                context,
 
263
                FLAGS.volume_topic,
 
264
                volume_id,
 
265
                snapshot_id,
 
266
                image_id,
 
267
                request_spec=request_spec,
 
268
                filter_properties=filter_properties)
226
269
 
227
270
    @wrap_check_policy
228
271
    def delete(self, context, volume, force=False):
267
310
        check_policy(context, 'get', volume)
268
311
        return volume
269
312
 
270
 
    def get_all(self, context, search_opts=None):
 
313
    def get_all(self, context, marker=None, limit=None, sort_key='created_at',
 
314
                sort_dir='desc', filters={}):
271
315
        check_policy(context, 'get_all')
272
316
 
273
 
        if search_opts is None:
274
 
            search_opts = {}
275
 
 
276
 
        if (context.is_admin and 'all_tenants' in search_opts):
 
317
        if (context.is_admin and 'all_tenants' in filters):
277
318
            # Need to remove all_tenants to pass the filtering below.
278
 
            del search_opts['all_tenants']
279
 
            volumes = self.db.volume_get_all(context)
 
319
            del filters['all_tenants']
 
320
            volumes = self.db.volume_get_all(context, marker, limit, sort_key,
 
321
                                             sort_dir)
280
322
        else:
281
323
            volumes = self.db.volume_get_all_by_project(context,
282
 
                                                        context.project_id)
283
 
        if search_opts:
284
 
            LOG.debug(_("Searching by: %s") % str(search_opts))
 
324
                                                        context.project_id,
 
325
                                                        marker, limit,
 
326
                                                        sort_key, sort_dir)
 
327
 
 
328
        if filters:
 
329
            LOG.debug(_("Searching by: %s") % str(filters))
285
330
 
286
331
            def _check_metadata_match(volume, searchdict):
287
332
                volume_metadata = {}
290
335
 
291
336
                for k, v in searchdict.iteritems():
292
337
                    if (k not in volume_metadata.keys() or
293
 
                        volume_metadata[k] != v):
 
338
                            volume_metadata[k] != v):
294
339
                        return False
295
340
                return True
296
341
 
301
346
            not_found = object()
302
347
            for volume in volumes:
303
348
                # go over all filters in the list
304
 
                for opt, values in search_opts.iteritems():
 
349
                for opt, values in filters.iteritems():
305
350
                    try:
306
351
                        filter_func = filter_mapping[opt]
307
352
                    except KeyError:
312
357
                else:  # did not break out loop
313
358
                    result.append(volume)  # volume matches all filters
314
359
            volumes = result
 
360
 
315
361
        return volumes
316
362
 
317
363
    def get_snapshot(self, context, snapshot_id):
319
365
        rv = self.db.snapshot_get(context, snapshot_id)
320
366
        return dict(rv.iteritems())
321
367
 
 
368
    def get_volume(self, context, volume_id):
 
369
        check_policy(context, 'get_volume')
 
370
        rv = self.db.volume_get(context, volume_id)
 
371
        return dict(rv.iteritems())
 
372
 
322
373
    def get_all_snapshots(self, context, search_opts=None):
323
374
        check_policy(context, 'get_all_snapshots')
324
375
 
384
435
    @wrap_check_policy
385
436
    def attach(self, context, volume, instance_uuid, mountpoint):
386
437
        return self.volume_rpcapi.attach_volume(context,
387
 
                                        volume,
388
 
                                        instance_uuid,
389
 
                                        mountpoint)
 
438
                                                volume,
 
439
                                                instance_uuid,
 
440
                                                mountpoint)
390
441
 
391
442
    @wrap_check_policy
392
443
    def detach(self, context, volume):
395
446
    @wrap_check_policy
396
447
    def initialize_connection(self, context, volume, connector):
397
448
        return self.volume_rpcapi.initialize_connection(context,
398
 
                                                volume,
399
 
                                                connector)
 
449
                                                        volume,
 
450
                                                        connector)
400
451
 
401
452
    @wrap_check_policy
402
453
    def terminate_connection(self, context, volume, connector, force=False):
403
454
        self.unreserve_volume(context, volume)
404
455
        return self.volume_rpcapi.terminate_connection(context,
405
 
                                                volume,
406
 
                                                connector,
407
 
                                                force)
 
456
                                                       volume,
 
457
                                                       connector,
 
458
                                                       force)
408
459
 
409
460
    def _create_snapshot(self, context, volume, name, description,
410
461
                         force=False):
414
465
            msg = _("must be available")
415
466
            raise exception.InvalidVolume(reason=msg)
416
467
 
417
 
        options = {
418
 
            'volume_id': volume['id'],
419
 
            'user_id': context.user_id,
420
 
            'project_id': context.project_id,
421
 
            'status': "creating",
422
 
            'progress': '0%',
423
 
            'volume_size': volume['size'],
424
 
            'display_name': name,
425
 
            'display_description': description}
 
468
        options = {'volume_id': volume['id'],
 
469
                   'user_id': context.user_id,
 
470
                   'project_id': context.project_id,
 
471
                   'status': "creating",
 
472
                   'progress': '0%',
 
473
                   'volume_size': volume['size'],
 
474
                   'display_name': name,
 
475
                   'display_description': description}
426
476
 
427
477
        snapshot = self.db.snapshot_create(context, options)
428
478
        self.volume_rpcapi.create_snapshot(context, volume, snapshot)
488
538
                    return i['value']
489
539
        return None
490
540
 
 
541
    @wrap_check_policy
 
542
    def get_volume_image_metadata(self, context, volume):
 
543
        db_data = self.db.volume_glance_metadata_get(context, volume['id'])
 
544
        return dict(
 
545
            (meta_entry.key, meta_entry.value) for meta_entry in db_data
 
546
        )
 
547
 
491
548
    def _check_volume_availability(self, context, volume, force):
492
549
        """Check if the volume can be used."""
493
550
        if volume['status'] not in ['available', 'in-use']:
504
561
 
505
562
        recv_metadata = self.image_service.create(context, metadata)
506
563
        self.update(context, volume, {'status': 'uploading'})
507
 
        self.volume_rpcapi.copy_volume_to_image(context, volume,
508
 
                                            recv_metadata['id'])
 
564
        self.volume_rpcapi.copy_volume_to_image(context,
 
565
                                                volume,
 
566
                                                recv_metadata['id'])
509
567
 
510
568
        response = {"id": volume['id'],
511
 
               "updated_at": volume['updated_at'],
512
 
               "status": 'uploading',
513
 
               "display_description": volume['display_description'],
514
 
               "size": volume['size'],
515
 
               "volume_type": volume['volume_type'],
516
 
               "image_id": recv_metadata['id'],
517
 
               "container_format": recv_metadata['container_format'],
518
 
               "disk_format": recv_metadata['disk_format'],
519
 
               "image_name": recv_metadata.get('name', None)
520
 
        }
 
569
                    "updated_at": volume['updated_at'],
 
570
                    "status": 'uploading',
 
571
                    "display_description": volume['display_description'],
 
572
                    "size": volume['size'],
 
573
                    "volume_type": volume['volume_type'],
 
574
                    "image_id": recv_metadata['id'],
 
575
                    "container_format": recv_metadata['container_format'],
 
576
                    "disk_format": recv_metadata['disk_format'],
 
577
                    "image_name": recv_metadata.get('name', None)}
521
578
        return response
522
579
 
523
580