36
37
FLAGS = flags.FLAGS
39
def _translate_attachment_detail_view(_context, vol):
40
"""Maps keys for attachment details view."""
42
d = _translate_attachment_summary_view(_context, vol)
44
# No additional data / lookups at the moment
49
def _translate_attachment_summary_view(_context, vol):
50
"""Maps keys for attachment summary view."""
55
# NOTE(justinsb): We use the volume id as the id of the attachment object
58
d['volume_id'] = volume_id
59
d['server_id'] = vol['instance_uuid']
60
if vol.get('mountpoint'):
61
d['device'] = vol['mountpoint']
66
def _translate_volume_detail_view(context, vol, image_id=None):
67
"""Maps keys for volumes details view."""
69
d = _translate_volume_summary_view(context, vol, image_id)
71
# No additional data / lookups at the moment
76
def _translate_volume_summary_view(context, vol, image_id=None):
77
"""Maps keys for volumes summary view."""
81
d['status'] = vol['status']
82
d['size'] = vol['size']
83
d['availability_zone'] = vol['availability_zone']
84
d['created_at'] = vol['created_at']
87
if vol['attach_status'] == 'attached':
88
attachment = _translate_attachment_detail_view(context, vol)
89
d['attachments'].append(attachment)
91
d['display_name'] = vol['display_name']
92
d['display_description'] = vol['display_description']
94
if vol['volume_type_id'] and vol.get('volume_type'):
95
d['volume_type'] = vol['volume_type']['name']
97
# TODO(bcwaldon): remove str cast once we use uuids
98
d['volume_type'] = str(vol['volume_type_id'])
100
d['snapshot_id'] = vol['snapshot_id']
103
d['image_id'] = image_id
105
LOG.audit(_("vol=%s"), vol, context=context)
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']
119
40
def make_attachment(elem):
121
42
elem.set('server_id')
176
98
volume_node = self.find_first_child_named(node, 'volume')
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):
220
144
except exception.NotFound:
221
145
raise exc.HTTPNotFound()
223
return {'volume': _translate_volume_detail_view(context, vol)}
147
return self._view_builder.detail(req, vol)
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)
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)
248
def _items(self, req, entity_maker):
249
"""Returns a list of volumes, transformed through entity_maker."""
252
search_opts.update(req.GET)
170
return self._get_volumes(req, is_detail=True)
172
def _get_volumes(self, req, is_detail):
173
"""Returns a list of volumes, transformed through view builder."""
254
175
context = req.environ['cinder.context']
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')
255
184
remove_invalid_options(context,
256
search_opts, self._get_volume_search_options())
258
volumes = self.volume_api.get_all(context, search_opts=search_opts)
185
filters, self._get_volume_filter_options())
187
# NOTE(thingee): v2 API allows name instead of display_name
188
if 'name' in filters:
189
filters['display_name'] = filters['name']
192
volumes = self.volume_api.get_all(context, marker, limit, sort_key,
259
194
limited_list = common.limited(volumes, req)
260
res = [entity_maker(context, vol) for vol in limited_list]
261
return {'volumes': res}
197
volumes = self._view_builder.detail_list(req, limited_list)
199
volumes = self._view_builder.summary_list(req, limited_list)
263
202
def _image_uuid_from_href(self, image_href):
264
203
# If the image href was generated by nova api, strip image_href
229
# NOTE(thingee): v2 API allows name instead of display_name
230
if volume.get('name'):
231
volume['display_name'] = volume.get('name')
290
234
req_volume_type = volume.get('volume_type', None)
291
235
if req_volume_type:
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)
306
250
kwargs['snapshot'] = None
252
source_volid = volume.get('source_volid')
253
if source_volid is not None:
254
kwargs['source_volume'] = self.volume_api.get_volume(context,
257
kwargs['source_volume'] = None
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']
312
265
LOG.audit(_("Create volume of %s GB"), size, context=context)
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()),
340
return {'volume': retval}
342
def _get_volume_search_options(self):
289
retval = self._view_builder.summary(req, dict(new_volume.iteritems()))
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')
346
297
@wsgi.serializers(xml=VolumeTemplate)
347
298
def update(self, req, id, body):
367
318
if key in volume:
368
319
update_dict[key] = volume[key]
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']
371
327
volume = self.volume_api.get(context, id)
372
328
self.volume_api.update(context, volume, update_dict)
376
332
volume.update(update_dict)
378
return {'volume': _translate_volume_detail_view(context, volume)}
334
return self._view_builder.detail(req, volume)
381
337
def create_resource(ext_mgr):
382
338
return wsgi.Resource(VolumeController(ext_mgr))
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
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]