cinder/cinder/volume/qos_specs.py
Michał Dulko e1b254dad1 Fix mistakes introduced with QoSSpecs object
If15ea8b628a6f88211a5d5cc7aadff44f7840138 introduced some mistakes that
I was able to identify when rebasing RequestSpec object patch:

* Missing loading volume_types relationship in QualityOfServiceSpecs
  _from_db_object.
* Wrong source of qos_specs relationship in VolumeType _from_db_object.
* Inefficient loading od QoSSpecs associations in db layer.
* Use of self in classmethod.

This commit fixes the issues.

Change-Id: I3b78127791a03e1a35a30d7f84f66e97d89c7cf9
Closes-Bug: 1603472
2016-07-28 13:34:24 +02:00

236 lines
8.3 KiB
Python

# 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 Implementation"""
from oslo_db import exception as db_exc
from oslo_log import log as logging
from cinder import context
from cinder import db
from cinder import exception
from cinder import objects
from cinder.i18n import _, _LE, _LW
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
CONTROL_LOCATION = ['front-end', 'back-end', 'both']
def create(context, name, specs=None):
"""Creates qos_specs.
:param specs dictionary that contains specifications for QoS
e.g. {'consumer': 'front-end',
'total_iops_sec': 1000,
'total_bytes_sec': 1024000}
"""
consumer = specs.get('consumer')
if consumer:
# If we need to modify specs, copy so we don't cause unintended
# consequences for the caller
specs = specs.copy()
del specs['consumer']
values = dict(name=name, consumer=consumer, specs=specs)
LOG.debug("Dict for qos_specs: %s", values)
qos_spec = objects.QualityOfServiceSpecs(context, **values)
qos_spec.create()
return qos_spec
def update(context, qos_specs_id, specs):
"""Update qos specs.
:param specs: dictionary that contains key/value pairs for updating
existing specs.
e.g. {'consumer': 'front-end',
'total_iops_sec': 500,
'total_bytes_sec': 512000,}
"""
LOG.debug('qos_specs.update(): specs %s' % specs)
try:
qos_spec = objects.QualityOfServiceSpecs.get_by_id(context,
qos_specs_id)
if 'consumer' in specs:
qos_spec.consumer = specs['consumer']
# If we need to modify specs, copy so we don't cause unintended
# consequences for the caller
specs = specs.copy()
del specs['consumer']
# Update any values in specs dict
qos_spec.specs.update(specs)
qos_spec.save()
except db_exc.DBError:
LOG.exception(_LE('DB error:'))
raise exception.QoSSpecsUpdateFailed(specs_id=qos_specs_id,
qos_specs=specs)
return qos_spec
def delete(context, qos_specs_id, force=False):
"""Marks qos specs as deleted.
'force' parameter is a flag to determine whether should destroy
should continue when there were entities associated with the qos specs.
force=True indicates caller would like to mark qos specs as deleted
even if there was entities associate with target qos specs.
Trying to delete a qos specs still associated with entities will
cause QoSSpecsInUse exception if force=False (default).
"""
if qos_specs_id is None:
msg = _("id cannot be None")
raise exception.InvalidQoSSpecs(reason=msg)
qos_spec = objects.QualityOfServiceSpecs.get_by_id(
context, qos_specs_id)
qos_spec.destroy(force)
def delete_keys(context, qos_specs_id, keys):
"""Marks specified key of target qos specs as deleted."""
if qos_specs_id is None:
msg = _("id cannot be None")
raise exception.InvalidQoSSpecs(reason=msg)
qos_spec = objects.QualityOfServiceSpecs.get_by_id(context, qos_specs_id)
# Previous behavior continued to delete keys until it hit first unset one,
# so for now will mimic that. In the future it would be useful to have all
# or nothing deletion of keys (or at least delete all set keys),
# especially since order of keys from CLI to API is not preserved currently
try:
for key in keys:
try:
del qos_spec.specs[key]
except KeyError:
raise exception.QoSSpecsKeyNotFound(
specs_key=key, specs_id=qos_specs_id)
finally:
qos_spec.save()
def get_associations(context, qos_specs_id):
"""Get all associations of given qos specs."""
try:
types = objects.VolumeTypeList.get_all_types_for_qos(context,
qos_specs_id)
except db_exc.DBError:
LOG.exception(_LE('DB error:'))
msg = _('Failed to get all associations of '
'qos specs %s') % qos_specs_id
LOG.warning(msg)
raise exception.CinderException(message=msg)
result = []
for vol_type in types:
result.append({
'association_type': 'volume_type',
'name': vol_type.name,
'id': vol_type.id
})
return result
def associate_qos_with_type(context, specs_id, type_id):
"""Associate qos_specs with volume type.
Associate target qos specs with specific volume type.
:param specs_id: qos specs ID to associate with
:param type_id: volume type ID to associate with
:raises VolumeTypeNotFound: if volume type doesn't exist
:raises QoSSpecsNotFound: if qos specs doesn't exist
:raises InvalidVolumeType: if volume type is already associated
with qos specs other than given one.
:raises QoSSpecsAssociateFailed: if there was general DB error
"""
try:
get_qos_specs(context, specs_id)
res = volume_types.get_volume_type_qos_specs(type_id)
if res.get('qos_specs', None):
if res['qos_specs'].get('id') != specs_id:
msg = (_("Type %(type_id)s is already associated with another "
"qos specs: %(qos_specs_id)s") %
{'type_id': type_id,
'qos_specs_id': res['qos_specs']['id']})
raise exception.InvalidVolumeType(reason=msg)
else:
db.qos_specs_associate(context, specs_id, type_id)
except db_exc.DBError:
LOG.exception(_LE('DB error:'))
LOG.warning(_LW('Failed to associate qos specs '
'%(id)s with type: %(vol_type_id)s'),
dict(id=specs_id, vol_type_id=type_id))
raise exception.QoSSpecsAssociateFailed(specs_id=specs_id,
type_id=type_id)
def disassociate_qos_specs(context, specs_id, type_id):
"""Disassociate qos_specs from volume type."""
try:
get_qos_specs(context, specs_id)
db.qos_specs_disassociate(context, specs_id, type_id)
except db_exc.DBError:
LOG.exception(_LE('DB error:'))
LOG.warning(_LW('Failed to disassociate qos specs '
'%(id)s with type: %(vol_type_id)s'),
dict(id=specs_id, vol_type_id=type_id))
raise exception.QoSSpecsDisassociateFailed(specs_id=specs_id,
type_id=type_id)
def disassociate_all(context, specs_id):
"""Disassociate qos_specs from all entities."""
try:
get_qos_specs(context, specs_id)
db.qos_specs_disassociate_all(context, specs_id)
except db_exc.DBError:
LOG.exception(_LE('DB error:'))
LOG.warning(_LW('Failed to disassociate qos specs %s.'), specs_id)
raise exception.QoSSpecsDisassociateFailed(specs_id=specs_id,
type_id=None)
def get_all_specs(context, filters=None, marker=None, limit=None, offset=None,
sort_keys=None, sort_dirs=None):
"""Get all non-deleted qos specs."""
return objects.QualityOfServiceSpecsList.get_all(
context, filters=filters, marker=marker, limit=limit, offset=offset,
sort_keys=sort_keys, sort_dirs=sort_dirs)
def get_qos_specs(ctxt, spec_id):
"""Retrieves single qos specs by id."""
if spec_id is None:
msg = _("id cannot be None")
raise exception.InvalidQoSSpecs(reason=msg)
if ctxt is None:
ctxt = context.get_admin_context()
return objects.QualityOfServiceSpecs.get_by_id(ctxt, spec_id)