cinder/cinder/volume/volume_types.py
Vivek Soni 32ea9339ea Volume type with encryption comparison
In volume retype with encryption, comparison of
volume types with same encryption will returns
true, instead of false because of unique field
'encryption_id', so ignore this field before
comparing.

Change-Id: I065b904bc881324de79ce59afd5bb97a97ea1b85
Closes-Bug: 1700741
2017-07-25 05:08:12 +00:00

378 lines
14 KiB
Python

# Copyright (c) 2011 Zadara Storage Inc.
# Copyright (c) 2011 OpenStack Foundation
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright (c) 2010 Citrix Systems, Inc.
# Copyright 2011 Ken Pepple
#
# 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.
"""Built-in volume type properties."""
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import uuidutils
from cinder import context
from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder import quota
from cinder import rpc
from cinder import utils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
QUOTAS = quota.QUOTAS
ENCRYPTION_IGNORED_FIELDS = ['volume_type_id', 'created_at', 'updated_at',
'deleted_at', 'encryption_id']
def create(context,
name,
extra_specs=None,
is_public=True,
projects=None,
description=None):
"""Creates volume types."""
extra_specs = extra_specs or {}
projects = projects or []
elevated = context if context.is_admin else context.elevated()
try:
type_ref = db.volume_type_create(elevated,
dict(name=name,
extra_specs=extra_specs,
is_public=is_public,
description=description),
projects=projects)
except db_exc.DBError:
LOG.exception('DB error:')
raise exception.VolumeTypeCreateFailed(name=name,
extra_specs=extra_specs)
return type_ref
def update(context, id, name, description, is_public=None):
"""Update volume type by id."""
if id is None:
msg = _("id cannot be None")
raise exception.InvalidVolumeType(reason=msg)
elevated = context if context.is_admin else context.elevated()
old_volume_type = get_volume_type(elevated, id)
try:
db.volume_type_update(elevated, id,
dict(name=name, description=description,
is_public=is_public))
# Rename resource in quota if volume type name is changed.
if name:
old_type_name = old_volume_type.get('name')
if old_type_name != name:
QUOTAS.update_quota_resource(elevated,
old_type_name,
name)
except db_exc.DBError:
LOG.exception('DB error:')
raise exception.VolumeTypeUpdateFailed(id=id)
def destroy(context, id):
"""Marks volume types as deleted."""
if id is None:
msg = _("id cannot be None")
raise exception.InvalidVolumeType(reason=msg)
elevated = context if context.is_admin else context.elevated()
return db.volume_type_destroy(elevated, id)
def get_all_types(context, inactive=0, filters=None, marker=None,
limit=None, sort_keys=None, sort_dirs=None,
offset=None, list_result=False):
"""Get all non-deleted volume_types.
Pass true as argument if you want deleted volume types returned also.
"""
vol_types = db.volume_type_get_all(context, inactive, filters=filters,
marker=marker, limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs, offset=offset,
list_result=list_result)
return vol_types
def get_all_types_by_group(context, group_id):
"""Get all volume_types in a group."""
vol_types = db.volume_type_get_all_by_group(context, group_id)
return vol_types
def get_volume_type(ctxt, id, expected_fields=None):
"""Retrieves single volume type by id."""
if id is None:
msg = _("id cannot be None")
raise exception.InvalidVolumeType(reason=msg)
if ctxt is None:
ctxt = context.get_admin_context()
return db.volume_type_get(ctxt, id, expected_fields=expected_fields)
def get_by_name_or_id(context, identity):
"""Retrieves volume type by id or name"""
if not uuidutils.is_uuid_like(identity):
return get_volume_type_by_name(context, identity)
return get_volume_type(context, identity)
def get_volume_type_by_name(context, name):
"""Retrieves single volume type by name."""
if name is None:
msg = _("name cannot be None")
raise exception.InvalidVolumeType(reason=msg)
return db.volume_type_get_by_name(context, name)
def get_default_volume_type():
"""Get the default volume type."""
name = CONF.default_volume_type
vol_type = {}
if name is not None:
ctxt = context.get_admin_context()
try:
vol_type = get_volume_type_by_name(ctxt, name)
except exception.VolumeTypeNotFoundByName:
# Couldn't find volume type with the name in default_volume_type
# flag, record this issue and move on
# TODO(zhiteng) consider add notification to warn admin
LOG.exception('Default volume type is not found. '
'Please check default_volume_type config:')
return vol_type
def get_volume_type_extra_specs(volume_type_id, key=False):
volume_type = get_volume_type(context.get_admin_context(),
volume_type_id)
extra_specs = volume_type['extra_specs']
if key:
if extra_specs.get(key):
return extra_specs.get(key)
else:
return False
else:
return extra_specs
def is_public_volume_type(context, volume_type_id):
"""Return is_public boolean value of volume type"""
volume_type = db.volume_type_get(context, volume_type_id)
return volume_type['is_public']
@utils.if_notifications_enabled
def notify_about_volume_type_access_usage(context,
volume_type_id,
project_id,
event_suffix,
host=None):
"""Notify about successful usage type-access-(add/remove) command.
:param context: security context
:param volume_type_id: volume type uuid
:param project_id: tenant uuid
:param event_suffix: name of called operation access-(add/remove)
:param host: hostname
"""
notifier_info = {'volume_type_id': volume_type_id,
'project_id': project_id}
if not host:
host = CONF.host
notifier = rpc.get_notifier("volume_type_project", host)
notifier.info(context,
'volume_type_project.%s' % event_suffix,
notifier_info)
def add_volume_type_access(context, volume_type_id, project_id):
"""Add access to volume type for project_id."""
if volume_type_id is None:
msg = _("volume_type_id cannot be None")
raise exception.InvalidVolumeType(reason=msg)
elevated = context if context.is_admin else context.elevated()
if is_public_volume_type(elevated, volume_type_id):
msg = _("Type access modification is not applicable to public volume "
"type.")
raise exception.InvalidVolumeType(reason=msg)
db.volume_type_access_add(elevated, volume_type_id, project_id)
notify_about_volume_type_access_usage(context,
volume_type_id,
project_id,
'access.add')
def remove_volume_type_access(context, volume_type_id, project_id):
"""Remove access to volume type for project_id."""
if volume_type_id is None:
msg = _("volume_type_id cannot be None")
raise exception.InvalidVolumeType(reason=msg)
elevated = context if context.is_admin else context.elevated()
if is_public_volume_type(elevated, volume_type_id):
msg = _("Type access modification is not applicable to public volume "
"type.")
raise exception.InvalidVolumeType(reason=msg)
db.volume_type_access_remove(elevated, volume_type_id, project_id)
notify_about_volume_type_access_usage(context,
volume_type_id,
project_id,
'access.remove')
def is_encrypted(context, volume_type_id):
return get_volume_type_encryption(context, volume_type_id) is not None
def get_volume_type_encryption(context, volume_type_id):
if volume_type_id is None:
return None
encryption = db.volume_type_encryption_get(context, volume_type_id)
return encryption
def get_volume_type_qos_specs(volume_type_id):
"""Get all qos specs for given volume type."""
ctxt = context.get_admin_context()
res = db.volume_type_qos_specs_get(ctxt,
volume_type_id)
return res
def volume_types_diff(context, vol_type_id1, vol_type_id2):
"""Returns a 'diff' of two volume types and whether they are equal.
Returns a tuple of (diff, equal), where 'equal' is a boolean indicating
whether there is any difference, and 'diff' is a dictionary with the
following format:
.. code-block:: default
{
'extra_specs': {'key1': (value_in_1st_vol_type,
value_in_2nd_vol_type),
'key2': (value_in_1st_vol_type,
value_in_2nd_vol_type),
{...}}
'qos_specs': {'key1': (value_in_1st_vol_type,
value_in_2nd_vol_type),
'key2': (value_in_1st_vol_type,
value_in_2nd_vol_type),
{...}}
'encryption': {'cipher': (value_in_1st_vol_type,
value_in_2nd_vol_type),
{'key_size': (value_in_1st_vol_type,
value_in_2nd_vol_type),
{...}}
}
"""
def _fix_qos_specs(qos_specs):
if qos_specs:
qos_specs.pop('id', None)
qos_specs.pop('name', None)
qos_specs.update(qos_specs.pop('specs', {}))
def _fix_encryption_specs(encryption):
if encryption:
encryption = dict(encryption)
for param in ENCRYPTION_IGNORED_FIELDS:
encryption.pop(param, None)
return encryption
def _dict_diff(dict1, dict2):
res = {}
equal = True
if dict1 is None:
dict1 = {}
if dict2 is None:
dict2 = {}
for k, v in dict1.items():
res[k] = (v, dict2.get(k))
if k not in dict2 or res[k][0] != res[k][1]:
equal = False
for k, v in dict2.items():
res[k] = (dict1.get(k), v)
if k not in dict1 or res[k][0] != res[k][1]:
equal = False
return (res, equal)
all_equal = True
diff = {}
vol_type_data = []
for vol_type_id in (vol_type_id1, vol_type_id2):
if vol_type_id is None:
specs = {'extra_specs': None,
'qos_specs': None,
'encryption': None}
else:
specs = {}
vol_type = get_volume_type(context, vol_type_id)
specs['extra_specs'] = vol_type.get('extra_specs')
qos_specs = get_volume_type_qos_specs(vol_type_id)
specs['qos_specs'] = qos_specs.get('qos_specs')
_fix_qos_specs(specs['qos_specs'])
specs['encryption'] = get_volume_type_encryption(context,
vol_type_id)
specs['encryption'] = _fix_encryption_specs(specs['encryption'])
vol_type_data.append(specs)
diff['extra_specs'], equal = _dict_diff(vol_type_data[0]['extra_specs'],
vol_type_data[1]['extra_specs'])
if not equal:
all_equal = False
diff['qos_specs'], equal = _dict_diff(vol_type_data[0]['qos_specs'],
vol_type_data[1]['qos_specs'])
if not equal:
all_equal = False
diff['encryption'], equal = _dict_diff(vol_type_data[0]['encryption'],
vol_type_data[1]['encryption'])
if not equal:
all_equal = False
return (diff, all_equal)
def volume_types_encryption_changed(context, vol_type_id1, vol_type_id2):
"""Return whether encryptions of two volume types are same."""
def _get_encryption(enc):
enc = dict(enc)
for param in ENCRYPTION_IGNORED_FIELDS:
enc.pop(param, None)
return enc
enc1 = get_volume_type_encryption(context, vol_type_id1)
enc2 = get_volume_type_encryption(context, vol_type_id2)
enc1_filtered = _get_encryption(enc1) if enc1 else None
enc2_filtered = _get_encryption(enc2) if enc2 else None
return enc1_filtered != enc2_filtered