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 Implementation""" 17 18 19from oslo_db import exception as db_exc 20from oslo_log import log as logging 21 22from cinder import context 23from cinder import db 24from cinder import exception 25from cinder.i18n import _ 26from cinder import objects 27from cinder import utils 28from cinder.volume import volume_types 29 30 31LOG = logging.getLogger(__name__) 32 33CONTROL_LOCATION = ['front-end', 'back-end', 'both'] 34 35 36def create(context, name, specs=None): 37 """Creates qos_specs. 38 39 :param specs: Dictionary that contains specifications for QoS 40 41 Expected format of the input parameter: 42 43 .. code-block:: python 44 45 { 46 'consumer': 'front-end', 47 'total_iops_sec': 1000, 48 'total_bytes_sec': 1024000 49 } 50 51 """ 52 # Validate the key-value pairs in the qos spec. 53 utils.validate_dictionary_string_length(specs) 54 55 consumer = specs.get('consumer') 56 if consumer: 57 # If we need to modify specs, copy so we don't cause unintended 58 # consequences for the caller 59 specs = specs.copy() 60 del specs['consumer'] 61 62 values = dict(name=name, consumer=consumer, specs=specs) 63 64 LOG.debug("Dict for qos_specs: %s", values) 65 qos_spec = objects.QualityOfServiceSpecs(context, **values) 66 qos_spec.create() 67 return qos_spec 68 69 70def update(context, qos_specs_id, specs): 71 """Update qos specs. 72 73 :param specs: dictionary that contains key/value pairs for updating 74 existing specs. 75 e.g. {'consumer': 'front-end', 76 'total_iops_sec': 500, 77 'total_bytes_sec': 512000,} 78 """ 79 LOG.debug('qos_specs.update(): specs %s', specs) 80 81 try: 82 utils.validate_dictionary_string_length(specs) 83 qos_spec = objects.QualityOfServiceSpecs.get_by_id(context, 84 qos_specs_id) 85 86 if 'consumer' in specs: 87 qos_spec.consumer = specs['consumer'] 88 # If we need to modify specs, copy so we don't cause unintended 89 # consequences for the caller 90 specs = specs.copy() 91 del specs['consumer'] 92 93 # Update any values in specs dict 94 qos_spec.specs.update(specs) 95 96 qos_spec.save() 97 except exception.InvalidInput as e: 98 raise exception.InvalidQoSSpecs(reason=e) 99 except db_exc.DBError: 100 LOG.exception('DB error:') 101 raise exception.QoSSpecsUpdateFailed(specs_id=qos_specs_id, 102 qos_specs=specs) 103 104 return qos_spec 105 106 107def delete(context, qos_specs_id, force=False): 108 """Marks qos specs as deleted. 109 110 'force' parameter is a flag to determine whether should destroy 111 should continue when there were entities associated with the qos specs. 112 force=True indicates caller would like to mark qos specs as deleted 113 even if there was entities associate with target qos specs. 114 Trying to delete a qos specs still associated with entities will 115 cause QoSSpecsInUse exception if force=False (default). 116 """ 117 if qos_specs_id is None: 118 msg = _("id cannot be None") 119 raise exception.InvalidQoSSpecs(reason=msg) 120 121 qos_spec = objects.QualityOfServiceSpecs.get_by_id( 122 context, qos_specs_id) 123 124 qos_spec.destroy(force) 125 126 127def delete_keys(context, qos_specs_id, keys): 128 """Marks specified key of target qos specs as deleted.""" 129 if qos_specs_id is None: 130 msg = _("id cannot be None") 131 raise exception.InvalidQoSSpecs(reason=msg) 132 133 qos_spec = objects.QualityOfServiceSpecs.get_by_id(context, qos_specs_id) 134 135 # Previous behavior continued to delete keys until it hit first unset one, 136 # so for now will mimic that. In the future it would be useful to have all 137 # or nothing deletion of keys (or at least delete all set keys), 138 # especially since order of keys from CLI to API is not preserved currently 139 try: 140 for key in keys: 141 try: 142 del qos_spec.specs[key] 143 except KeyError: 144 raise exception.QoSSpecsKeyNotFound( 145 specs_key=key, specs_id=qos_specs_id) 146 finally: 147 qos_spec.save() 148 149 150def get_associations(context, qos_specs_id): 151 """Get all associations of given qos specs.""" 152 try: 153 types = objects.VolumeTypeList.get_all_types_for_qos(context, 154 qos_specs_id) 155 except db_exc.DBError: 156 LOG.exception('DB error:') 157 msg = _('Failed to get all associations of ' 158 'qos specs %s') % qos_specs_id 159 LOG.warning(msg) 160 raise exception.CinderException(message=msg) 161 162 result = [] 163 for vol_type in types: 164 result.append({ 165 'association_type': 'volume_type', 166 'name': vol_type.name, 167 'id': vol_type.id 168 }) 169 170 return result 171 172 173def associate_qos_with_type(context, specs_id, type_id): 174 """Associate qos_specs with volume type. 175 176 Associate target qos specs with specific volume type. 177 178 :param specs_id: qos specs ID to associate with 179 :param type_id: volume type ID to associate with 180 :raises VolumeTypeNotFound: if volume type doesn't exist 181 :raises QoSSpecsNotFound: if qos specs doesn't exist 182 :raises InvalidVolumeType: if volume type is already associated 183 with qos specs other than given one. 184 :raises QoSSpecsAssociateFailed: if there was general DB error 185 """ 186 try: 187 get_qos_specs(context, specs_id) 188 res = volume_types.get_volume_type_qos_specs(type_id) 189 if res.get('qos_specs', None): 190 if res['qos_specs'].get('id') != specs_id: 191 msg = (_("Type %(type_id)s is already associated with another " 192 "qos specs: %(qos_specs_id)s") % 193 {'type_id': type_id, 194 'qos_specs_id': res['qos_specs']['id']}) 195 raise exception.InvalidVolumeType(reason=msg) 196 else: 197 db.qos_specs_associate(context, specs_id, type_id) 198 except db_exc.DBError: 199 LOG.exception('DB error:') 200 LOG.warning('Failed to associate qos specs ' 201 '%(id)s with type: %(vol_type_id)s', 202 dict(id=specs_id, vol_type_id=type_id)) 203 raise exception.QoSSpecsAssociateFailed(specs_id=specs_id, 204 type_id=type_id) 205 206 207def disassociate_qos_specs(context, specs_id, type_id): 208 """Disassociate qos_specs from volume type.""" 209 try: 210 get_qos_specs(context, specs_id) 211 db.qos_specs_disassociate(context, specs_id, type_id) 212 except db_exc.DBError: 213 LOG.exception('DB error:') 214 LOG.warning('Failed to disassociate qos specs ' 215 '%(id)s with type: %(vol_type_id)s', 216 dict(id=specs_id, vol_type_id=type_id)) 217 raise exception.QoSSpecsDisassociateFailed(specs_id=specs_id, 218 type_id=type_id) 219 220 221def disassociate_all(context, specs_id): 222 """Disassociate qos_specs from all entities.""" 223 try: 224 get_qos_specs(context, specs_id) 225 db.qos_specs_disassociate_all(context, specs_id) 226 except db_exc.DBError: 227 LOG.exception('DB error:') 228 LOG.warning('Failed to disassociate qos specs %s.', specs_id) 229 raise exception.QoSSpecsDisassociateFailed(specs_id=specs_id, 230 type_id=None) 231 232 233def get_all_specs(context, filters=None, marker=None, limit=None, offset=None, 234 sort_keys=None, sort_dirs=None): 235 """Get all non-deleted qos specs.""" 236 return objects.QualityOfServiceSpecsList.get_all( 237 context, filters=filters, marker=marker, limit=limit, offset=offset, 238 sort_keys=sort_keys, sort_dirs=sort_dirs) 239 240 241def get_qos_specs(ctxt, spec_id): 242 """Retrieves single qos specs by id.""" 243 if spec_id is None: 244 msg = _("id cannot be None") 245 raise exception.InvalidQoSSpecs(reason=msg) 246 247 if ctxt is None: 248 ctxt = context.get_admin_context() 249 250 return objects.QualityOfServiceSpecs.get_by_id(ctxt, spec_id) 251