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