# Copyright (c) 2013 eBay Inc. # Copyright (c) 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """The QoS specs extension""" import webob from cinder.api import extensions from cinder.api.openstack import wsgi from cinder.api.views import qos_specs as view_qos_specs from cinder.api import xmlutil from cinder import exception from cinder.openstack.common import log as logging from cinder.openstack.common.notifier import api as notifier_api from cinder.openstack.common import strutils from cinder.volume import qos_specs LOG = logging.getLogger(__name__) authorize = extensions.extension_authorizer('volume', 'qos_specs_manage') def make_qos_specs(elem): elem.set('id') elem.set('name') elem.set('consumer') elem.append(SpecsTemplate()) def make_associations(elem): elem.set('association_type') elem.set('name') elem.set('id') class SpecsTemplate(xmlutil.TemplateBuilder): def construct(self): return xmlutil.MasterTemplate(xmlutil.make_flat_dict('specs'), 1) class QoSSpecsTemplate(xmlutil.TemplateBuilder): def construct(self): root = xmlutil.TemplateElement('qos_specs') elem = xmlutil.SubTemplateElement(root, 'qos_spec', selector='qos_specs') make_qos_specs(elem) return xmlutil.MasterTemplate(root, 1) class AssociationsTemplate(xmlutil.TemplateBuilder): def construct(self): root = xmlutil.TemplateElement('qos_associations') elem = xmlutil.SubTemplateElement(root, 'associations', selector='qos_associations') make_associations(elem) return xmlutil.MasterTemplate(root, 1) def _check_specs(context, specs_id): try: qos_specs.get_qos_specs(context, specs_id) except exception.NotFound as ex: raise webob.exc.HTTPNotFound(explanation=unicode(ex)) class QoSSpecsController(wsgi.Controller): """The volume type extra specs API controller for the OpenStack API.""" _view_builder_class = view_qos_specs.ViewBuilder @staticmethod def _notify_qos_specs_error(context, method, payload): notifier_api.notify(context, 'QoSSpecs', method, notifier_api.ERROR, payload) @wsgi.serializers(xml=QoSSpecsTemplate) def index(self, req): """Returns the list of qos_specs.""" context = req.environ['cinder.context'] authorize(context) specs = qos_specs.get_all_specs(context) return self._view_builder.summary_list(req, specs) @wsgi.serializers(xml=QoSSpecsTemplate) def create(self, req, body=None): context = req.environ['cinder.context'] authorize(context) if not self.is_valid_body(body, 'qos_specs'): raise webob.exc.HTTPBadRequest() specs = body['qos_specs'] name = specs.get('name', None) if name is None or name == "": msg = _("Please specify a name for QoS specs.") raise webob.exc.HTTPBadRequest(explanation=msg) try: qos_specs.create(context, name, specs) spec = qos_specs.get_qos_specs_by_name(context, name) notifier_info = dict(name=name, specs=specs) notifier_api.notify(context, 'QoSSpecs', 'QoSSpecs.create', notifier_api.INFO, notifier_info) except exception.InvalidInput as err: notifier_err = dict(name=name, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.create', notifier_err) raise webob.exc.HTTPBadRequest(explanation=err) except exception.QoSSpecsExists as err: notifier_err = dict(name=name, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.create', notifier_err) raise webob.exc.HTTPConflict(explanation=err) except exception.QoSSpecsCreateFailed as err: notifier_err = dict(name=name, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.create', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return self._view_builder.detail(req, spec) @wsgi.serializers(xml=QoSSpecsTemplate) def update(self, req, id, body=None): context = req.environ['cinder.context'] authorize(context) if not self.is_valid_body(body, 'qos_specs'): raise webob.exc.HTTPBadRequest() specs = body['qos_specs'] try: qos_specs.update(context, id, specs) notifier_info = dict(id=id, specs=specs) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.update', notifier_api.INFO, notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.update', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.InvalidQoSSpecs as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.update', notifier_err) raise webob.exc.HTTPBadRequest(explanation=err) except exception.QoSSpecsUpdateFailed as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.update', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return body @wsgi.serializers(xml=QoSSpecsTemplate) def show(self, req, id): """Return a single qos spec item.""" context = req.environ['cinder.context'] authorize(context) try: spec = qos_specs.get_qos_specs(context, id) except exception.QoSSpecsNotFound as err: raise webob.exc.HTTPNotFound(explanation=err) return self._view_builder.detail(req, spec) def delete(self, req, id): """Deletes an existing qos specs.""" context = req.environ['cinder.context'] authorize(context) force = req.params.get('force', None) #convert string to bool type in strict manner force = strutils.bool_from_string(force) LOG.debug("Delete qos_spec: %(id)s, force: %(force)s" % {'id': id, 'force': force}) try: qos_specs.delete(context, id, force) notifier_info = dict(id=id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.delete', notifier_api.INFO, notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsInUse as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) if force: msg = _('Failed to disassociate qos specs.') raise webob.exc.HTTPInternalServerError(explanation=msg) msg = _('Qos specs still in use.') raise webob.exc.HTTPBadRequest(explanation=msg) return webob.Response(status_int=202) def delete_keys(self, req, id, body): """Deletes specified keys in qos specs.""" context = req.environ['cinder.context'] authorize(context) if not (body and 'keys' in body and isinstance(body.get('keys'), list)): raise webob.exc.HTTPBadRequest() keys = body['keys'] LOG.debug("Delete_key spec: %(id)s, keys: %(keys)s" % {'id': id, 'keys': keys}) try: qos_specs.delete_keys(context, id, keys) notifier_info = dict(id=id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.delete_keys', notifier_api.INFO, notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete_keys', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsKeyNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete_keys', notifier_err) raise webob.exc.HTTPBadRequest(explanation=err) return webob.Response(status_int=202) @wsgi.serializers(xml=AssociationsTemplate) def associations(self, req, id): """List all associations of given qos specs.""" context = req.environ['cinder.context'] authorize(context) LOG.debug("Get associations for qos_spec id: %s" % id) try: associates = qos_specs.get_associations(context, id) notifier_info = dict(id=id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.associations', notifier_api.INFO, notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associations', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.CinderException as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associations', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return self._view_builder.associations(req, associates) def associate(self, req, id): """Associate a qos specs with a volume type.""" context = req.environ['cinder.context'] authorize(context) type_id = req.params.get('vol_type_id', None) if not type_id: msg = _('Volume Type id must not be None.') notifier_err = dict(id=id, error_message=msg) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) raise webob.exc.HTTPBadRequest(explanation=msg) LOG.debug("Associate qos_spec: %(id)s with type: %(type_id)s" % {'id': id, 'type_id': type_id}) try: qos_specs.associate_qos_with_type(context, id, type_id) notifier_info = dict(id=id, type_id=type_id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.associate', notifier_api.INFO, notifier_info) except exception.VolumeTypeNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.InvalidVolumeType as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) raise webob.exc.HTTPBadRequest(explanation=err) except exception.QoSSpecsAssociateFailed as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return webob.Response(status_int=202) def disassociate(self, req, id): """Disassociate a qos specs from a volume type.""" context = req.environ['cinder.context'] authorize(context) type_id = req.params.get('vol_type_id', None) if not type_id: msg = _('Volume Type id must not be None.') notifier_err = dict(id=id, error_message=msg) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) raise webob.exc.HTTPBadRequest(explanation=msg) LOG.debug("Disassociate qos_spec: %(id)s from type: %(type_id)s" % {'id': id, 'type_id': type_id}) try: qos_specs.disassociate_qos_specs(context, id, type_id) notifier_info = dict(id=id, type_id=type_id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.disassociate', notifier_api.INFO, notifier_info) except exception.VolumeTypeNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsDisassociateFailed as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return webob.Response(status_int=202) def disassociate_all(self, req, id): """Disassociate a qos specs from all volume types.""" context = req.environ['cinder.context'] authorize(context) LOG.debug("Disassociate qos_spec: %s from all." % id) try: qos_specs.disassociate_all(context, id) notifier_info = dict(id=id) notifier_api.notify(context, 'QoSSpecs', 'qos_specs.disassociate_all', notifier_api.INFO, notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.disassociate_all', notifier_err) raise webob.exc.HTTPNotFound(explanation=err) except exception.QoSSpecsDisassociateFailed as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.disassociate_all', notifier_err) raise webob.exc.HTTPInternalServerError(explanation=err) return webob.Response(status_int=202) class Qos_specs_manage(extensions.ExtensionDescriptor): """QoS specs support.""" name = "Qos_specs_manage" alias = "qos-specs" namespace = "http://docs.openstack.org/volume/ext/qos-specs/api/v1" updated = "2013-08-02T00:00:00+00:00" def get_resources(self): resources = [] res = extensions.ResourceExtension( Qos_specs_manage.alias, QoSSpecsController(), member_actions={"associations": "GET", "associate": "GET", "disassociate": "GET", "disassociate_all": "GET", "delete_keys": "PUT"}) resources.append(res) return resources