OpenStack Block Storage (Cinder)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
7.8 KiB

# 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
# 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.
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_versionedobjects import fields
from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.objects import base
from cinder.objects import fields as c_fields
LOG = logging.getLogger(__name__)
class QualityOfServiceSpecs(base.CinderPersistentObject,
# Version
# 1.0: Initial version
VERSION = "1.0"
OPTIONAL_FIELDS = ['volume_types']
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(),
'consumer': c_fields.QoSConsumerField(
'specs': fields.DictOfNullableStringsField(nullable=True),
'volume_types': fields.ObjectField('VolumeTypeList', nullable=True),
def __init__(self, *args, **kwargs):
super(QualityOfServiceSpecs, self).__init__(*args, **kwargs)
self._init_specs = {}
def __setattr__(self, name, value):
super(QualityOfServiceSpecs, self).__setattr__(name, value)
except ValueError:
if name == 'consumer':
# Give more descriptive error message for invalid 'consumer'
msg = (_("Valid consumer of QoS specs are: %s") %
raise exception.InvalidQoSSpecs(reason=msg)
def obj_reset_changes(self, fields=None, recursive=False):
super(QualityOfServiceSpecs, self).obj_reset_changes(fields, recursive)
if fields is None or 'specs' in fields:
self._init_specs = self.specs.copy() if self.specs else {}
def obj_what_changed(self):
changes = super(QualityOfServiceSpecs, self).obj_what_changed()
# Do comparison of what's in the dict vs. reference to the specs object
if self.obj_attr_is_set('id'):
if self.specs != self._init_specs:
# If both dicts are equal don't consider anything gets changed
if 'specs' in changes:
return changes
def obj_get_changes(self):
changes = super(QualityOfServiceSpecs, self).obj_get_changes()
if 'specs' in changes:
# For specs, we only want what has changed in the dictionary,
# because otherwise we'll individually overwrite the DB value for
# every key in 'specs' even if it hasn't changed
specs_changes = {}
for key, val in self.specs.items():
if val != self._init_specs.get(key):
specs_changes[key] = val
changes['specs'] = specs_changes
specs_keys_removed = (set(self._init_specs.keys()) -
if specs_keys_removed:
# Special key notifying which specs keys have been deleted
changes['specs_keys_removed'] = specs_keys_removed
return changes
def obj_load_attr(self, attrname):
if attrname not in self.OPTIONAL_FIELDS:
raise exception.ObjectActionError(
reason=_('attribute %s not lazy-loadable') % attrname)
if not self._context:
raise exception.OrphanedObjectError(method='obj_load_attr',
if attrname == 'volume_types':
self.volume_types = objects.VolumeTypeList.get_all_types_for_qos(
def _from_db_object(cls, context, qos_spec, db_qos_spec,
if expected_attrs is None:
expected_attrs = []
for name, field in qos_spec.fields.items():
if name not in cls.OPTIONAL_FIELDS:
value = db_qos_spec.get(name)
# 'specs' could be null if only a consumer is given, so make
# it an empty dict instead of None
if not value and isinstance(field, fields.DictOfStringsField):
value = {}
setattr(qos_spec, name, value)
if 'volume_types' in expected_attrs:
volume_types = objects.VolumeTypeList.get_all_types_for_qos(
context, db_qos_spec['id'])
qos_spec.volume_types = volume_types
qos_spec._context = context
return qos_spec
def create(self):
if self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='create',
reason='already created')
updates = self.cinder_obj_get_changes()
create_ret = db.qos_specs_create(self._context, updates)
except db_exc.DBDataError:
msg = _('Error writing field to database')
raise exception.Invalid(msg)
except db_exc.DBError:
LOG.exception('DB error occurred when creating QoS specs.')
raise exception.QoSSpecsCreateFailed(,
# Save ID with the object
updates['id'] = create_ret['id']
self._from_db_object(self._context, self, updates)
def save(self):
updates = self.cinder_obj_get_changes()
if updates:
if 'specs_keys_removed' in updates.keys():
for specs_key_to_remove in updates['specs_keys_removed']:
self._context,, specs_key_to_remove)
del updates['specs_keys_removed']
db.qos_specs_update(self._context,, updates)
def destroy(self, force=False):
"""Deletes the QoS spec.
:param force: when force is True, all volume_type mappings for this QoS
are deleted. When force is False and volume_type
mappings still exist, a QoSSpecsInUse exception is thrown
if self.volume_types:
if not force:
raise exception.QoSSpecsInUse(
# remove all association
updated_values = db.qos_specs_delete(self._context,
class QualityOfServiceSpecsList(base.ObjectListBase, base.CinderObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('QualityOfServiceSpecs'),
def get_all(cls, context, *args, **kwargs):
specs = db.qos_specs_get_all(context, *args, **kwargs)
return base.obj_make_list(context, cls(context),
objects.QualityOfServiceSpecs, specs)