1# Copyright (c) 2013 eBay Inc. 2# Copyright (c) 2013 OpenStack Foundation 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16"""The QoS specs extension""" 17 18from oslo_log import log as logging 19from oslo_utils import timeutils 20import six 21from six.moves import http_client 22import webob 23 24from cinder.api import common 25from cinder.api import extensions 26from cinder.api.openstack import wsgi 27from cinder.api.views import qos_specs as view_qos_specs 28from cinder import exception 29from cinder.i18n import _ 30from cinder.policies import qos_specs as policy 31from cinder import rpc 32from cinder import utils 33from cinder.volume import qos_specs 34 35 36LOG = logging.getLogger(__name__) 37 38 39def _check_specs(context, specs_id): 40 # Not found exception will be handled at the wsgi level 41 qos_specs.get_qos_specs(context, specs_id) 42 43 44class QoSSpecsController(wsgi.Controller): 45 """The volume type extra specs API controller for the OpenStack API.""" 46 47 _view_builder_class = view_qos_specs.ViewBuilder 48 49 @staticmethod 50 @utils.if_notifications_enabled 51 def _notify_qos_specs_error(context, method, payload): 52 rpc.get_notifier('QoSSpecs').error(context, 53 method, 54 payload) 55 56 def index(self, req): 57 """Returns the list of qos_specs.""" 58 context = req.environ['cinder.context'] 59 context.authorize(policy.GET_ALL_POLICY) 60 61 params = req.params.copy() 62 63 marker, limit, offset = common.get_pagination_params(params) 64 sort_keys, sort_dirs = common.get_sort_params(params) 65 filters = params 66 allowed_search_options = ('id', 'name', 'consumer') 67 utils.remove_invalid_filter_options(context, filters, 68 allowed_search_options) 69 70 specs = qos_specs.get_all_specs(context, filters=filters, 71 marker=marker, limit=limit, 72 offset=offset, sort_keys=sort_keys, 73 sort_dirs=sort_dirs) 74 return self._view_builder.summary_list(req, specs) 75 76 def create(self, req, body=None): 77 context = req.environ['cinder.context'] 78 context.authorize(policy.CREATE_POLICY) 79 80 self.assert_valid_body(body, 'qos_specs') 81 82 specs = body['qos_specs'] 83 name = specs.pop('name', None) 84 if name is None: 85 msg = _("Please specify a name for QoS specs.") 86 raise webob.exc.HTTPBadRequest(explanation=msg) 87 88 self.validate_string_length(name, 'name', min_length=1, 89 max_length=255, remove_whitespaces=True) 90 name = name.strip() 91 92 try: 93 spec = qos_specs.create(context, name, specs) 94 notifier_info = dict(name=name, 95 created_at=spec.created_at, 96 specs=specs) 97 rpc.get_notifier('QoSSpecs').info(context, 98 'qos_specs.create', 99 notifier_info) 100 except exception.InvalidQoSSpecs as err: 101 notifier_err = dict(name=name, error_message=err) 102 self._notify_qos_specs_error(context, 103 'qos_specs.create', 104 notifier_err) 105 raise webob.exc.HTTPBadRequest(explanation=six.text_type(err)) 106 except exception.QoSSpecsExists as err: 107 notifier_err = dict(name=name, error_message=err) 108 self._notify_qos_specs_error(context, 109 'qos_specs.create', 110 notifier_err) 111 raise webob.exc.HTTPConflict(explanation=six.text_type(err)) 112 except exception.QoSSpecsCreateFailed as err: 113 notifier_err = dict(name=name, error_message=err) 114 self._notify_qos_specs_error(context, 115 'qos_specs.create', 116 notifier_err) 117 raise webob.exc.HTTPInternalServerError( 118 explanation=six.text_type(err)) 119 120 return self._view_builder.detail(req, spec) 121 122 def update(self, req, id, body=None): 123 context = req.environ['cinder.context'] 124 context.authorize(policy.UPDATE_POLICY) 125 self.assert_valid_body(body, 'qos_specs') 126 specs = body['qos_specs'] 127 try: 128 spec = qos_specs.get_qos_specs(context, id) 129 130 qos_specs.update(context, id, specs) 131 notifier_info = dict(id=id, 132 created_at=spec.created_at, 133 updated_at=timeutils.utcnow(), 134 specs=specs) 135 rpc.get_notifier('QoSSpecs').info(context, 136 'qos_specs.update', 137 notifier_info) 138 except (exception.QoSSpecsNotFound, exception.InvalidQoSSpecs) as err: 139 notifier_err = dict(id=id, error_message=err) 140 self._notify_qos_specs_error(context, 141 'qos_specs.update', 142 notifier_err) 143 # Not found exception will be handled at the wsgi level 144 raise 145 except exception.QoSSpecsUpdateFailed as err: 146 notifier_err = dict(id=id, error_message=err) 147 self._notify_qos_specs_error(context, 148 'qos_specs.update', 149 notifier_err) 150 raise webob.exc.HTTPInternalServerError( 151 explanation=six.text_type(err)) 152 153 return body 154 155 def show(self, req, id): 156 """Return a single qos spec item.""" 157 context = req.environ['cinder.context'] 158 context.authorize(policy.GET_POLICY) 159 160 # Not found exception will be handled at the wsgi level 161 spec = qos_specs.get_qos_specs(context, id) 162 163 return self._view_builder.detail(req, spec) 164 165 def delete(self, req, id): 166 """Deletes an existing qos specs.""" 167 context = req.environ['cinder.context'] 168 context.authorize(policy.DELETE_POLICY) 169 170 # Convert string to bool type in strict manner 171 force = utils.get_bool_param('force', req.params) 172 LOG.debug("Delete qos_spec: %(id)s, force: %(force)s", 173 {'id': id, 'force': force}) 174 try: 175 spec = qos_specs.get_qos_specs(context, id) 176 177 qos_specs.delete(context, id, force) 178 notifier_info = dict(id=id, 179 created_at=spec.created_at, 180 deleted_at=timeutils.utcnow()) 181 rpc.get_notifier('QoSSpecs').info(context, 182 'qos_specs.delete', 183 notifier_info) 184 except exception.QoSSpecsNotFound as err: 185 notifier_err = dict(id=id, error_message=err) 186 self._notify_qos_specs_error(context, 187 'qos_specs.delete', 188 notifier_err) 189 # Not found exception will be handled at the wsgi level 190 raise 191 except exception.QoSSpecsInUse as err: 192 notifier_err = dict(id=id, error_message=err) 193 self._notify_qos_specs_error(context, 194 'qos_specs.delete', 195 notifier_err) 196 if force: 197 msg = _('Failed to disassociate qos specs.') 198 raise webob.exc.HTTPInternalServerError(explanation=msg) 199 msg = _('Qos specs still in use.') 200 raise webob.exc.HTTPBadRequest(explanation=msg) 201 202 return webob.Response(status_int=http_client.ACCEPTED) 203 204 def delete_keys(self, req, id, body): 205 """Deletes specified keys in qos specs.""" 206 context = req.environ['cinder.context'] 207 context.authorize(policy.DELETE_POLICY) 208 209 if not (body and 'keys' in body 210 and isinstance(body.get('keys'), list)): 211 raise webob.exc.HTTPBadRequest() 212 213 keys = body['keys'] 214 LOG.debug("Delete_key spec: %(id)s, keys: %(keys)s", 215 {'id': id, 'keys': keys}) 216 217 try: 218 qos_specs.delete_keys(context, id, keys) 219 spec = qos_specs.get_qos_specs(context, id) 220 notifier_info = dict(id=id, 221 created_at=spec.created_at, 222 updated_at=spec.updated_at) 223 rpc.get_notifier('QoSSpecs').info(context, 'qos_specs.delete_keys', 224 notifier_info) 225 except exception.NotFound as err: 226 notifier_err = dict(id=id, error_message=err) 227 self._notify_qos_specs_error(context, 228 'qos_specs.delete_keys', 229 notifier_err) 230 # Not found exception will be handled at the wsgi level 231 raise 232 233 return webob.Response(status_int=http_client.ACCEPTED) 234 235 def associations(self, req, id): 236 """List all associations of given qos specs.""" 237 context = req.environ['cinder.context'] 238 context.authorize(policy.GET_ALL_POLICY) 239 240 LOG.debug("Get associations for qos_spec id: %s", id) 241 242 try: 243 spec = qos_specs.get_qos_specs(context, id) 244 245 associates = qos_specs.get_associations(context, id) 246 notifier_info = dict(id=id, 247 created_at=spec.created_at) 248 rpc.get_notifier('QoSSpecs').info(context, 249 'qos_specs.associations', 250 notifier_info) 251 except exception.QoSSpecsNotFound as err: 252 notifier_err = dict(id=id, error_message=err) 253 self._notify_qos_specs_error(context, 254 'qos_specs.associations', 255 notifier_err) 256 # Not found exception will be handled at the wsgi level 257 raise 258 except exception.CinderException as err: 259 notifier_err = dict(id=id, error_message=err) 260 self._notify_qos_specs_error(context, 261 'qos_specs.associations', 262 notifier_err) 263 raise webob.exc.HTTPInternalServerError( 264 explanation=six.text_type(err)) 265 266 return self._view_builder.associations(req, associates) 267 268 def associate(self, req, id): 269 """Associate a qos specs with a volume type.""" 270 context = req.environ['cinder.context'] 271 context.authorize(policy.UPDATE_POLICY) 272 273 type_id = req.params.get('vol_type_id', None) 274 275 if not type_id: 276 msg = _('Volume Type id must not be None.') 277 notifier_err = dict(id=id, error_message=msg) 278 self._notify_qos_specs_error(context, 279 'qos_specs.delete', 280 notifier_err) 281 raise webob.exc.HTTPBadRequest(explanation=msg) 282 LOG.debug("Associate qos_spec: %(id)s with type: %(type_id)s", 283 {'id': id, 'type_id': type_id}) 284 285 try: 286 spec = qos_specs.get_qos_specs(context, id) 287 288 qos_specs.associate_qos_with_type(context, id, type_id) 289 notifier_info = dict(id=id, type_id=type_id, 290 created_at=spec.created_at) 291 rpc.get_notifier('QoSSpecs').info(context, 292 'qos_specs.associate', 293 notifier_info) 294 except exception.NotFound as err: 295 notifier_err = dict(id=id, error_message=err) 296 self._notify_qos_specs_error(context, 297 'qos_specs.associate', 298 notifier_err) 299 # Not found exception will be handled at the wsgi level 300 raise 301 except exception.InvalidVolumeType as err: 302 notifier_err = dict(id=id, error_message=err) 303 self._notify_qos_specs_error(context, 304 'qos_specs.associate', 305 notifier_err) 306 self._notify_qos_specs_error(context, 307 'qos_specs.associate', 308 notifier_err) 309 raise webob.exc.HTTPBadRequest(explanation=six.text_type(err)) 310 except exception.QoSSpecsAssociateFailed as err: 311 notifier_err = dict(id=id, error_message=err) 312 self._notify_qos_specs_error(context, 313 'qos_specs.associate', 314 notifier_err) 315 raise webob.exc.HTTPInternalServerError( 316 explanation=six.text_type(err)) 317 318 return webob.Response(status_int=http_client.ACCEPTED) 319 320 def disassociate(self, req, id): 321 """Disassociate a qos specs from a volume type.""" 322 context = req.environ['cinder.context'] 323 context.authorize(policy.UPDATE_POLICY) 324 325 type_id = req.params.get('vol_type_id', None) 326 327 if not type_id: 328 msg = _('Volume Type id must not be None.') 329 notifier_err = dict(id=id, error_message=msg) 330 self._notify_qos_specs_error(context, 331 'qos_specs.delete', 332 notifier_err) 333 raise webob.exc.HTTPBadRequest(explanation=msg) 334 LOG.debug("Disassociate qos_spec: %(id)s from type: %(type_id)s", 335 {'id': id, 'type_id': type_id}) 336 337 try: 338 spec = qos_specs.get_qos_specs(context, id) 339 340 qos_specs.disassociate_qos_specs(context, id, type_id) 341 notifier_info = dict(id=id, type_id=type_id, 342 created_at=spec.created_at) 343 rpc.get_notifier('QoSSpecs').info(context, 344 'qos_specs.disassociate', 345 notifier_info) 346 except exception.NotFound as err: 347 notifier_err = dict(id=id, error_message=err) 348 self._notify_qos_specs_error(context, 349 'qos_specs.disassociate', 350 notifier_err) 351 # Not found exception will be handled at the wsgi level 352 raise 353 except exception.QoSSpecsDisassociateFailed as err: 354 notifier_err = dict(id=id, error_message=err) 355 self._notify_qos_specs_error(context, 356 'qos_specs.disassociate', 357 notifier_err) 358 raise webob.exc.HTTPInternalServerError( 359 explanation=six.text_type(err)) 360 361 return webob.Response(status_int=http_client.ACCEPTED) 362 363 def disassociate_all(self, req, id): 364 """Disassociate a qos specs from all volume types.""" 365 context = req.environ['cinder.context'] 366 context.authorize(policy.UPDATE_POLICY) 367 368 LOG.debug("Disassociate qos_spec: %s from all.", id) 369 370 try: 371 spec = qos_specs.get_qos_specs(context, id) 372 373 qos_specs.disassociate_all(context, id) 374 notifier_info = dict(id=id, 375 created_at=spec.created_at) 376 rpc.get_notifier('QoSSpecs').info(context, 377 'qos_specs.disassociate_all', 378 notifier_info) 379 except exception.QoSSpecsNotFound as err: 380 notifier_err = dict(id=id, error_message=err) 381 self._notify_qos_specs_error(context, 382 'qos_specs.disassociate_all', 383 notifier_err) 384 # Not found exception will be handled at the wsgi level 385 raise 386 except exception.QoSSpecsDisassociateFailed as err: 387 notifier_err = dict(id=id, error_message=err) 388 self._notify_qos_specs_error(context, 389 'qos_specs.disassociate_all', 390 notifier_err) 391 raise webob.exc.HTTPInternalServerError( 392 explanation=six.text_type(err)) 393 394 return webob.Response(status_int=http_client.ACCEPTED) 395 396 397class Qos_specs_manage(extensions.ExtensionDescriptor): 398 """QoS specs support.""" 399 400 name = "Qos_specs_manage" 401 alias = "qos-specs" 402 updated = "2013-08-02T00:00:00+00:00" 403 404 def get_resources(self): 405 resources = [] 406 res = extensions.ResourceExtension( 407 Qos_specs_manage.alias, 408 QoSSpecsController(), 409 member_actions={"associations": "GET", 410 "associate": "GET", 411 "disassociate": "GET", 412 "disassociate_all": "GET", 413 "delete_keys": "PUT"}) 414 415 resources.append(res) 416 417 return resources 418