1# Copyright 2015 IBM Corp.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""API over the cinder service."""
15
16from django.utils.translation import ugettext_lazy as _
17from django.views import generic
18
19from openstack_dashboard import api
20from openstack_dashboard.api.rest import json_encoder
21from openstack_dashboard.api.rest import urls
22from openstack_dashboard.api.rest import utils as rest_utils
23from openstack_dashboard.usage import quotas
24
25
26CLIENT_KEYWORDS = {'marker', 'sort_dir', 'paginate'}
27
28
29@urls.register
30class Volumes(generic.View):
31    """API for cinder volumes."""
32    url_regex = r'cinder/volumes/$'
33
34    @rest_utils.ajax()
35    def get(self, request):
36        """Get a detailed list of volumes associated with the current project.
37
38        Example GET:
39        http://localhost/api/cinder/volumes?paginate=true&sort_dir=asc
40
41        If invoked as an admin, you may set the GET parameter "all_projects"
42        to 'true' to return details for all projects.
43
44        The following get parameters may be passed in the GET
45
46        :param search_opts: includes options such as name, status, bootable
47        :param paginate: If true will perform pagination based on settings.
48        :param marker: Specifies the namespace of the last-seen image.
49             The typical pattern of limit and marker is to make an
50             initial limited request and then to use the last
51             namespace from the response as the marker parameter
52             in a subsequent limited request. With paginate, limit
53             is automatically set.
54        :param sort_dir: The sort direction ('asc' or 'desc').
55
56        The listing result is an object with property "items".
57        """
58
59        if request.GET.get('all_projects') == 'true':
60            result, has_more, has_prev = api.cinder.volume_list_paged(
61                request,
62                {'all_tenants': 1}
63            )
64        else:
65            search_opts, kwargs = rest_utils.parse_filters_kwargs(
66                request, CLIENT_KEYWORDS)
67            result, has_more, has_prev = api.cinder.volume_list_paged(
68                request,
69                search_opts=search_opts, **kwargs
70            )
71        return {
72            'items': [api.cinder.Volume(u).to_dict() for u in result],
73            'has_more_data': has_more,
74            'has_prev_data': has_prev
75        }
76
77    @rest_utils.ajax(data_required=True)
78    def post(self, request):
79        volume = api.cinder.volume_create(
80            request,
81            size=request.DATA['size'],
82            name=request.DATA['name'],
83            description=request.DATA['description'],
84            volume_type=request.DATA['volume_type'],
85            snapshot_id=request.DATA['snapshot_id'],
86            metadata=request.DATA['metadata'],
87            image_id=request.DATA['image_id'],
88            availability_zone=request.DATA['availability_zone'],
89            source_volid=request.DATA['source_volid']
90        )
91
92        return rest_utils.CreatedResponse(
93            '/api/cinder/volumes/%s' % volume.id,
94            volume.to_dict()
95        )
96
97
98@urls.register
99class Volume(generic.View):
100    """API for cinder volume."""
101    url_regex = r'cinder/volumes/(?P<volume_id>[^/]+)/$'
102
103    @rest_utils.ajax()
104    def get(self, request, volume_id):
105        """Get a single volume's details with the volume id.
106
107        The following get parameters may be passed in the GET
108
109        :param volume_id: the id of the volume
110
111        The result is a volume object.
112        """
113        return api.cinder.volume_get(request, volume_id).to_dict()
114
115
116@urls.register
117class VolumeTypes(generic.View):
118    """API for volume types."""
119    url_regex = r'cinder/volumetypes/$'
120
121    @rest_utils.ajax()
122    def get(self, request):
123        """Get a list of volume types.
124
125        The listing result is an object with the property "items".
126        """
127        result = api.cinder.volume_type_list(request)
128        return {'items': [api.cinder.VolumeType(u).to_dict() for u in result]}
129
130
131@urls.register
132class VolumeMetadata(generic.View):
133    """API for volume metadata"""
134    url_regex = r'cinder/volumes/(?P<volume_id>[^/]+)/metadata$'
135
136    @rest_utils.ajax()
137    def get(self, request, volume_id):
138        """Get a specific volume's metadata
139
140        http://localhost/api/cinder/volumes/1/metadata
141        """
142        return api.cinder.volume_get(request,
143                                     volume_id).to_dict().get('metadata')
144
145    @rest_utils.ajax()
146    def patch(self, request, volume_id):
147        """Update metadata items for specific volume
148
149        http://localhost/api/cinder/volumes/1/metadata
150        """
151        updated = request.DATA['updated']
152        removed = request.DATA['removed']
153        if updated:
154            api.cinder.volume_set_metadata(request, volume_id, updated)
155        if removed:
156            api.cinder.volume_delete_metadata(request, volume_id, removed)
157
158
159@urls.register
160class VolumeType(generic.View):
161    """API for getting a volume type."""
162    url_regex = r'cinder/volumetypes/(?P<volumetype_id>[^/]+)/$'
163
164    @rest_utils.ajax()
165    def get(self, request, volumetype_id):
166        """Get a single volume type details with the volume type id.
167
168        The following get parameters may be passed in the GET
169
170        :param volumetype_id: the id of the volume type
171
172        If 'default' is passed as the volumetype_id then
173        it returns the default volumetype
174
175        The result is a volume type object.
176        """
177        if volumetype_id == 'default':
178            volumetype = api.cinder.volume_type_default(request)
179        else:
180            volumetype = api.cinder.volume_type_get(request, volumetype_id)
181
182        return api.cinder.VolumeType(volumetype).to_dict()
183
184
185@urls.register
186class VolumeSnapshots(generic.View):
187    """API for cinder volume snapshots."""
188    url_regex = r'cinder/volumesnapshots/$'
189
190    @rest_utils.ajax()
191    def get(self, request):
192        """Get a list of volume snapshots associated with the current project.
193
194        The listing result is an object with property "items".
195        """
196        result = api.cinder.volume_snapshot_list(
197            request,
198            search_opts=rest_utils.parse_filters_kwargs(request)[0]
199        )
200        return {'items': [u.to_dict() for u in result]}
201
202
203@urls.register
204class VolumeSnapshotMetadata(generic.View):
205    """API for getting snapshots metadata"""
206    url_regex = r'cinder/volumesnapshots/' \
207                r'(?P<volume_snapshot_id>[^/]+)/metadata$'
208
209    @rest_utils.ajax()
210    def get(self, request, volume_snapshot_id):
211        """Get a specific volumes snapshot metadata
212
213        http://localhost/api/cinder/volumesnapshots/1/metadata
214        """
215        result = api.cinder.volume_snapshot_get(request,
216                                                volume_snapshot_id).\
217            to_dict().get('metadata')
218        return result
219
220    @rest_utils.ajax()
221    def patch(self, request, volume_snapshot_id):
222        """Update metadata for specific volume snapshot
223
224        http://localhost/api/cinder/volumesnapshots/1/metadata
225        """
226        updated = request.DATA['updated']
227        removed = request.DATA['removed']
228        if updated:
229            api.cinder.volume_snapshot_set_metadata(request,
230                                                    volume_snapshot_id,
231                                                    updated)
232        if removed:
233            api.cinder.volume_snapshot_delete_metadata(request,
234                                                       volume_snapshot_id,
235                                                       removed)
236
237
238@urls.register
239class VolumeTypeMetadata(generic.View):
240    """API for getting snapshots metadata"""
241    url_regex = r'cinder/volumetypes/(?P<volume_type_id>[^/]+)/metadata$'
242
243    @rest_utils.ajax()
244    def get(self, request, volume_type_id):
245        """Get a specific volume's metadata
246
247        http://localhost/api/cinder/volumetypes/1/metadata
248        """
249        metadata = api.cinder.volume_type_extra_get(request, volume_type_id)
250        result = {x.key: x.value for x in metadata}
251        return result
252
253    @rest_utils.ajax()
254    def patch(self, request, volume_type_id):
255        """Update metadata for specific volume
256
257        http://localhost/api/cinder/volumetypes/1/metadata
258        """
259        updated = request.DATA['updated']
260        removed = request.DATA['removed']
261        if updated:
262            api.cinder.volume_type_extra_set(request,
263                                             volume_type_id,
264                                             updated)
265        if removed:
266            api.cinder.volume_type_extra_delete(request,
267                                                volume_type_id,
268                                                removed)
269
270
271@urls.register
272class Extensions(generic.View):
273    # API for cinder extensions.
274    url_regex = r'cinder/extensions/$'
275
276    @rest_utils.ajax()
277    def get(self, request):
278        """Get a list of extensions.
279
280        The listing result is an object with property "items". Each item is
281        an extension.
282
283        Example GET:
284        http://localhost/api/cinder/extensions
285        """
286        result = api.cinder.list_extensions(request)
287        return {'items': [{
288            'alias': e.alias,
289            'description': e.description,
290            'links': e.links,
291            'name': e.name,
292            'updated': e.updated
293
294        } for e in result]}
295
296
297@urls.register
298class QoSSpecs(generic.View):
299    url_regex = r'cinder/qosspecs/$'
300
301    @rest_utils.ajax()
302    def get(self, request):
303        result = api.cinder.qos_specs_list(request)
304        return {'items': [u.to_dict() for u in result]}
305
306
307@urls.register
308class TenantAbsoluteLimits(generic.View):
309    url_regex = r'cinder/tenantabsolutelimits/$'
310
311    @rest_utils.ajax(json_encoder=json_encoder.NaNJSONEncoder)
312    def get(self, request):
313        return api.cinder.tenant_absolute_limits(request)
314
315
316@urls.register
317class Services(generic.View):
318    """API for cinder services."""
319    url_regex = r'cinder/services/$'
320
321    @rest_utils.ajax()
322    def get(self, request):
323        """Get a list of cinder services.
324
325        Will return HTTP 501 status code if the service_list extension is
326        not supported.
327        """
328        if not (api.base.is_service_enabled(request, 'volume') and
329                api.cinder.extension_supported(request, 'Services')):
330            raise rest_utils.AjaxError(501, '')
331
332        result = api.cinder.service_list(request)
333        return {'items': [{
334            'binary': u.binary,
335            'host': u.host,
336            'zone': u.zone,
337            'updated_at': u.updated_at,
338            'status': u.status,
339            'state': u.state,
340            'id': idx + 1
341        } for idx, u in enumerate(result)]}
342
343
344@urls.register
345class DefaultQuotaSets(generic.View):
346    """API for getting default quotas for cinder"""
347    url_regex = r'cinder/quota-sets/defaults/$'
348
349    @rest_utils.ajax()
350    def get(self, request):
351        """Get the values for Cinder specific quotas
352
353        Example GET:
354        http://localhost/api/cinder/quota-sets/defaults/
355        """
356        if not api.cinder.is_volume_service_enabled(request):
357            raise rest_utils.AjaxError(501, _('Service Cinder is disabled.'))
358        quota_set = api.cinder.default_quota_get(
359            request, request.user.tenant_id)
360
361        result = [
362            {
363                'display_name':
364                quotas.QUOTA_NAMES.get(
365                    quota.name,
366                    quota.name.replace("_", " ").title()
367                ) + '',
368                'name': quota.name,
369                'limit': quota.limit
370            }
371            for quota in quota_set]
372        return {'items': result}
373
374    @rest_utils.ajax(data_required=True)
375    def patch(self, request):
376        """Update the values for Cinder specific quotas
377
378        This method returns HTTP 204 (no content) on success.
379        """
380        if api.cinder.is_volume_service_enabled(request):
381            cinder_data = {
382                key: request.DATA[key] for key in quotas.CINDER_QUOTA_FIELDS
383            }
384
385            api.cinder.default_quota_update(request, **cinder_data)
386        else:
387            raise rest_utils.AjaxError(501, _('Service Cinder is disabled.'))
388
389
390@urls.register
391class QuotaSets(generic.View):
392    """API for setting quotas for a given project."""
393    url_regex = r'cinder/quota-sets/(?P<project_id>[0-9a-f]+)$'
394
395    @rest_utils.ajax(data_required=True)
396    def patch(self, request, project_id):
397        """Update a single project quota data.
398
399        The PATCH data should be an application/json object with the
400        attributes to set to new quota values.
401
402        This method returns HTTP 204 (no content) on success.
403        """
404        # Filters cinder quota fields
405        disabled_quotas = quotas.get_disabled_quotas(request)
406
407        if api.cinder.is_volume_service_enabled(request):
408            cinder_data = {
409                key: request.DATA[key] for key in quotas.CINDER_QUOTA_FIELDS
410                if key not in disabled_quotas
411            }
412
413            api.cinder.tenant_quota_update(request, project_id, **cinder_data)
414        else:
415            raise rest_utils.AjaxError(501, _('Service Cinder is disabled.'))
416
417
418@urls.register
419class AvailabilityZones(generic.View):
420    """API for cinder availability zones."""
421    url_regex = r'cinder/availzones/$'
422
423    @rest_utils.ajax()
424    def get(self, request):
425        """Get a list of availability zones.
426
427        The following get parameters may be passed in the GET
428        request:
429
430        :param detailed: If this equals "true" then the result will
431            include more detail.
432
433        The listing result is an object with property "items".
434        """
435        detailed = request.GET.get('detailed') == 'true'
436        result = api.cinder.availability_zone_list(request, detailed)
437        return {'items': [u.to_dict() for u in result]}
438