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

« back to all changes in this revision

Viewing changes to cinder/api/v2/volumes.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
from cinder.api import common
23
23
from cinder.api.openstack import wsgi
 
24
from cinder.api.v2.views import volumes as volume_views
24
25
from cinder.api import xmlutil
25
26
from cinder import exception
26
27
from cinder import flags
36
37
FLAGS = flags.FLAGS
37
38
 
38
39
 
39
 
def _translate_attachment_detail_view(_context, vol):
40
 
    """Maps keys for attachment details view."""
41
 
 
42
 
    d = _translate_attachment_summary_view(_context, vol)
43
 
 
44
 
    # No additional data / lookups at the moment
45
 
 
46
 
    return d
47
 
 
48
 
 
49
 
def _translate_attachment_summary_view(_context, vol):
50
 
    """Maps keys for attachment summary view."""
51
 
    d = {}
52
 
 
53
 
    volume_id = vol['id']
54
 
 
55
 
    # NOTE(justinsb): We use the volume id as the id of the attachment object
56
 
    d['id'] = volume_id
57
 
 
58
 
    d['volume_id'] = volume_id
59
 
    d['server_id'] = vol['instance_uuid']
60
 
    if vol.get('mountpoint'):
61
 
        d['device'] = vol['mountpoint']
62
 
 
63
 
    return d
64
 
 
65
 
 
66
 
def _translate_volume_detail_view(context, vol, image_id=None):
67
 
    """Maps keys for volumes details view."""
68
 
 
69
 
    d = _translate_volume_summary_view(context, vol, image_id)
70
 
 
71
 
    # No additional data / lookups at the moment
72
 
 
73
 
    return d
74
 
 
75
 
 
76
 
def _translate_volume_summary_view(context, vol, image_id=None):
77
 
    """Maps keys for volumes summary view."""
78
 
    d = {}
79
 
 
80
 
    d['id'] = vol['id']
81
 
    d['status'] = vol['status']
82
 
    d['size'] = vol['size']
83
 
    d['availability_zone'] = vol['availability_zone']
84
 
    d['created_at'] = vol['created_at']
85
 
 
86
 
    d['attachments'] = []
87
 
    if vol['attach_status'] == 'attached':
88
 
        attachment = _translate_attachment_detail_view(context, vol)
89
 
        d['attachments'].append(attachment)
90
 
 
91
 
    d['display_name'] = vol['display_name']
92
 
    d['display_description'] = vol['display_description']
93
 
 
94
 
    if vol['volume_type_id'] and vol.get('volume_type'):
95
 
        d['volume_type'] = vol['volume_type']['name']
96
 
    else:
97
 
        # TODO(bcwaldon): remove str cast once we use uuids
98
 
        d['volume_type'] = str(vol['volume_type_id'])
99
 
 
100
 
    d['snapshot_id'] = vol['snapshot_id']
101
 
 
102
 
    if image_id:
103
 
        d['image_id'] = image_id
104
 
 
105
 
    LOG.audit(_("vol=%s"), vol, context=context)
106
 
 
107
 
    if vol.get('volume_metadata'):
108
 
        metadata = vol.get('volume_metadata')
109
 
        d['metadata'] = dict((item['key'], item['value']) for item in metadata)
110
 
    # avoid circular ref when vol is a Volume instance
111
 
    elif vol.get('metadata') and isinstance(vol.get('metadata'), dict):
112
 
        d['metadata'] = vol['metadata']
113
 
    else:
114
 
        d['metadata'] = {}
115
 
 
116
 
    return d
117
 
 
118
 
 
119
40
def make_attachment(elem):
120
41
    elem.set('id')
121
42
    elem.set('server_id')
129
50
    elem.set('size')
130
51
    elem.set('availability_zone')
131
52
    elem.set('created_at')
132
 
    elem.set('display_name')
 
53
    elem.set('name')
133
54
    elem.set('display_description')
134
55
    elem.set('volume_type')
135
56
    elem.set('snapshot_id')
 
57
    elem.set('source_volid')
136
58
 
137
59
    attachments = xmlutil.SubTemplateElement(elem, 'attachments')
138
60
    attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
175
97
        volume = {}
176
98
        volume_node = self.find_first_child_named(node, 'volume')
177
99
 
178
 
        attributes = ['display_name', 'display_description', 'size',
 
100
        attributes = ['name', 'display_description', 'size',
179
101
                      'volume_type', 'availability_zone']
180
102
        for attr in attributes:
181
103
            if volume_node.getAttribute(attr):
205
127
class VolumeController(wsgi.Controller):
206
128
    """The Volumes API controller for the OpenStack API."""
207
129
 
 
130
    _view_builder_class = volume_views.ViewBuilder
 
131
 
208
132
    def __init__(self, ext_mgr):
209
133
        self.volume_api = volume.API()
210
134
        self.ext_mgr = ext_mgr
220
144
        except exception.NotFound:
221
145
            raise exc.HTTPNotFound()
222
146
 
223
 
        return {'volume': _translate_volume_detail_view(context, vol)}
 
147
        return self._view_builder.detail(req, vol)
224
148
 
225
149
    def delete(self, req, id):
226
150
        """Delete a volume."""
238
162
    @wsgi.serializers(xml=VolumesTemplate)
239
163
    def index(self, req):
240
164
        """Returns a summary list of volumes."""
241
 
        return self._items(req, entity_maker=_translate_volume_summary_view)
 
165
        return self._get_volumes(req, is_detail=False)
242
166
 
243
167
    @wsgi.serializers(xml=VolumesTemplate)
244
168
    def detail(self, req):
245
169
        """Returns a detailed list of volumes."""
246
 
        return self._items(req, entity_maker=_translate_volume_detail_view)
247
 
 
248
 
    def _items(self, req, entity_maker):
249
 
        """Returns a list of volumes, transformed through entity_maker."""
250
 
 
251
 
        search_opts = {}
252
 
        search_opts.update(req.GET)
 
170
        return self._get_volumes(req, is_detail=True)
 
171
 
 
172
    def _get_volumes(self, req, is_detail):
 
173
        """Returns a list of volumes, transformed through view builder."""
253
174
 
254
175
        context = req.environ['cinder.context']
 
176
 
 
177
        params = req.params.copy()
 
178
        marker = params.pop('marker', None)
 
179
        limit = params.pop('limit', None)
 
180
        sort_key = params.pop('sort_key', 'created_at')
 
181
        sort_dir = params.pop('sort_dir', 'desc')
 
182
        filters = params
 
183
 
255
184
        remove_invalid_options(context,
256
 
                               search_opts, self._get_volume_search_options())
257
 
 
258
 
        volumes = self.volume_api.get_all(context, search_opts=search_opts)
 
185
                               filters, self._get_volume_filter_options())
 
186
 
 
187
        # NOTE(thingee): v2 API allows name instead of display_name
 
188
        if 'name' in filters:
 
189
            filters['display_name'] = filters['name']
 
190
            del filters['name']
 
191
 
 
192
        volumes = self.volume_api.get_all(context, marker, limit, sort_key,
 
193
                                          sort_dir, filters)
259
194
        limited_list = common.limited(volumes, req)
260
 
        res = [entity_maker(context, vol) for vol in limited_list]
261
 
        return {'volumes': res}
 
195
 
 
196
        if is_detail:
 
197
            volumes = self._view_builder.detail_list(req, limited_list)
 
198
        else:
 
199
            volumes = self._view_builder.summary_list(req, limited_list)
 
200
        return volumes
262
201
 
263
202
    def _image_uuid_from_href(self, image_href):
264
203
        # If the image href was generated by nova api, strip image_href
287
226
 
288
227
        kwargs = {}
289
228
 
 
229
        # NOTE(thingee): v2 API allows name instead of display_name
 
230
        if volume.get('name'):
 
231
            volume['display_name'] = volume.get('name')
 
232
            del volume['name']
 
233
 
290
234
        req_volume_type = volume.get('volume_type', None)
291
235
        if req_volume_type:
292
236
            try:
293
237
                kwargs['volume_type'] = volume_types.get_volume_type_by_name(
294
 
                        context, req_volume_type)
 
238
                    context, req_volume_type)
295
239
            except exception.VolumeTypeNotFound:
296
240
                explanation = 'Volume type not found.'
297
241
                raise exc.HTTPNotFound(explanation=explanation)
305
249
        else:
306
250
            kwargs['snapshot'] = None
307
251
 
 
252
        source_volid = volume.get('source_volid')
 
253
        if source_volid is not None:
 
254
            kwargs['source_volume'] = self.volume_api.get_volume(context,
 
255
                                                                 source_volid)
 
256
        else:
 
257
            kwargs['source_volume'] = None
 
258
 
308
259
        size = volume.get('size', None)
309
260
        if size is None and kwargs['snapshot'] is not None:
310
261
            size = kwargs['snapshot']['volume_size']
 
262
        elif size is None and kwargs['source_volume'] is not None:
 
263
            size = kwargs['source_volume']['size']
311
264
 
312
265
        LOG.audit(_("Create volume of %s GB"), size, context=context)
313
266
 
333
286
        # TODO(vish): Instance should be None at db layer instead of
334
287
        #             trying to lazy load, but for now we turn it into
335
288
        #             a dict to avoid an error.
336
 
        retval = _translate_volume_detail_view(context,
337
 
                                               dict(new_volume.iteritems()),
338
 
                                               image_uuid)
339
 
 
340
 
        return {'volume': retval}
341
 
 
342
 
    def _get_volume_search_options(self):
 
289
        retval = self._view_builder.summary(req, dict(new_volume.iteritems()))
 
290
 
 
291
        return retval
 
292
 
 
293
    def _get_volume_filter_options(self):
343
294
        """Return volume search options allowed by non-admin."""
344
 
        return ('display_name', 'status')
 
295
        return ('name', 'status')
345
296
 
346
297
    @wsgi.serializers(xml=VolumeTemplate)
347
298
    def update(self, req, id, body):
358
309
        update_dict = {}
359
310
 
360
311
        valid_update_keys = (
361
 
            'display_name',
 
312
            'name',
362
313
            'display_description',
363
314
            'metadata',
364
315
        )
367
318
            if key in volume:
368
319
                update_dict[key] = volume[key]
369
320
 
 
321
        # NOTE(thingee): v2 API allows name instead of display_name
 
322
        if 'name' in update_dict:
 
323
            update_dict['display_name'] = update_dict['name']
 
324
            del update_dict['name']
 
325
 
370
326
        try:
371
327
            volume = self.volume_api.get(context, id)
372
328
            self.volume_api.update(context, volume, update_dict)
375
331
 
376
332
        volume.update(update_dict)
377
333
 
378
 
        return {'volume': _translate_volume_detail_view(context, volume)}
 
334
        return self._view_builder.detail(req, volume)
379
335
 
380
336
 
381
337
def create_resource(ext_mgr):
382
338
    return wsgi.Resource(VolumeController(ext_mgr))
383
339
 
384
340
 
385
 
def remove_invalid_options(context, search_options, allowed_search_options):
 
341
def remove_invalid_options(context, filters, allowed_search_options):
386
342
    """Remove search options that are not valid for non-admin API/context."""
387
343
    if context.is_admin:
388
344
        # Allow all options
389
345
        return
390
346
    # Otherwise, strip out all unknown options
391
 
    unknown_options = [opt for opt in search_options
392
 
            if opt not in allowed_search_options]
 
347
    unknown_options = [opt for opt in filters
 
348
                       if opt not in allowed_search_options]
393
349
    bad_options = ", ".join(unknown_options)
394
 
    log_msg = _("Removing options '%(bad_options)s' from query") % locals()
 
350
    log_msg = _("Removing options '%s' from query") % bad_options
395
351
    LOG.debug(log_msg)
396
352
    for opt in unknown_options:
397
 
        del search_options[opt]
 
353
        del filters[opt]