Add volume type support
This patch adds support for Cinder VolumeType's extra specs and QoS specs. These parameters must be set when creating a volume, and must be passed as dictionaries using parameters "extra_specs" and "qos_specs" on the "create_volume" call.
This commit is contained in:
parent
9f8d4b8753
commit
e53227af2a
|
@ -301,10 +301,32 @@ class Volume(NamedObject):
|
|||
self._snapshots = []
|
||||
self._connections = []
|
||||
|
||||
qos_specs = kwargs.pop('qos_specs', None)
|
||||
extra_specs = kwargs.pop('extra_specs', {})
|
||||
|
||||
super(Volume, self).__init__(backend_or_vol, **kwargs)
|
||||
self._populate_data()
|
||||
self.local_attach = None
|
||||
|
||||
if qos_specs or extra_specs:
|
||||
if qos_specs:
|
||||
qos_specs = cinder_objs.QualityOfServiceSpecs(
|
||||
id=self.id, name=self.id,
|
||||
consumer='back-end', specs=qos_specs)
|
||||
qos_specs_id = self.id
|
||||
else:
|
||||
qos_specs = qos_specs_id = None
|
||||
|
||||
self._ovo.volume_type = cinder_objs.VolumeType(
|
||||
context=self.CONTEXT,
|
||||
is_public=True,
|
||||
id=self.id,
|
||||
name=self.id,
|
||||
qos_specs_id=qos_specs_id,
|
||||
extra_specs=extra_specs,
|
||||
qos_specs=qos_specs)
|
||||
self._ovo.volume_type_id = self.id
|
||||
|
||||
@property
|
||||
def snapshots(self):
|
||||
# Lazy loading
|
||||
|
|
|
@ -21,6 +21,8 @@ from oslo_utils import timeutils
|
|||
from oslo_versionedobjects import fields
|
||||
import six
|
||||
|
||||
from cinderlib import serialization
|
||||
|
||||
|
||||
class PersistenceDriverBase(object):
|
||||
"""Provide Metadata Persistency for our resources.
|
||||
|
@ -51,6 +53,10 @@ class PersistenceDriverBase(object):
|
|||
|
||||
def set_volume(self, volume):
|
||||
self.reset_change_tracker(volume)
|
||||
if volume.volume_type:
|
||||
volume.volume_type.obj_reset_changes()
|
||||
if volume.volume_type.qos_specs_id:
|
||||
volume.volume_type.qos_specs.obj_reset_changes()
|
||||
|
||||
def set_snapshot(self, snapshot):
|
||||
self.reset_change_tracker(snapshot)
|
||||
|
@ -94,6 +100,13 @@ class PersistenceDriverBase(object):
|
|||
result = {key: getattr(resource._ovo, key)
|
||||
for key in resource._changed_fields
|
||||
if not isinstance(resource.fields[key], fields.ObjectField)}
|
||||
if getattr(resource._ovo, 'volume_type_id', None):
|
||||
if ('qos_specs' in resource.volume_type._changed_fields
|
||||
and resource.volume_type.qos_specs):
|
||||
result['qos_specs'] = resource._ovo.volume_type.qos_specs.specs
|
||||
if ('extra_specs' in resource.volume_type._changed_fields
|
||||
and resource.volume_type.extra_specs):
|
||||
result['extra_specs'] = resource._ovo.volume_type.extra_specs
|
||||
return result
|
||||
|
||||
def get_fields(self, resource):
|
||||
|
@ -104,6 +117,10 @@ class PersistenceDriverBase(object):
|
|||
key not in getattr(resource, 'obj_extra_fields', []) and not
|
||||
isinstance(resource.fields[key], fields.ObjectField))
|
||||
}
|
||||
if getattr(resource._ovo, 'volume_type_id', None):
|
||||
result['extra_specs'] = resource._ovo.volume_type.extra_specs
|
||||
if resource._ovo.volume_type.qos_specs_id:
|
||||
result['qos_specs'] = resource._ovo.volume_type.qos_specs.specs
|
||||
return result
|
||||
|
||||
|
||||
|
@ -142,6 +159,27 @@ class DB(object):
|
|||
def snapshot_get(self, context, snapshot_id, *args, **kwargs):
|
||||
return self.persistence.get_snapshots(snapshot_id)[0]._ovo
|
||||
|
||||
def get_volume_type(self, context, id, inactive=False,
|
||||
expected_fields=None):
|
||||
res = self.persistence.get_volumes(id)[0]._ovo
|
||||
if not res.volume_type_id:
|
||||
return None
|
||||
return self._vol_type_to_dict(res.volume_type)
|
||||
|
||||
def qos_specs_get(self, context, qos_specs_id, inactive=False):
|
||||
res = self.persistence.get_volumes(qos_specs_id)[0]._ovo
|
||||
if not res.volume_type_id:
|
||||
return None
|
||||
return self._vol_type_to_dict(res.volume_type)['qos_specs']
|
||||
|
||||
@staticmethod
|
||||
def _vol_type_to_dict(volume_type):
|
||||
res = serialization.obj_to_primitive(volume_type)
|
||||
res = res['versioned_object.data']
|
||||
if res.get('qos_specs'):
|
||||
res['qos_specs'] = res['qos_specs']['versioned_object.data']
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def image_volume_cache_get_by_volume_id(cls, context, volume_id):
|
||||
return None
|
||||
|
|
|
@ -88,6 +88,10 @@ class DBPersistence(persistence_base.PersistenceDriverBase):
|
|||
ovos = cinder_objs.VolumeList.get_all(objects.CONTEXT, filters=filters)
|
||||
result = [objects.Volume(ovo.availability_zone, __ovo=ovo)
|
||||
for ovo in ovos.objects]
|
||||
for r in result:
|
||||
if r.volume_type_id:
|
||||
r.volume_type.extra_specs # Trigger extra specs load
|
||||
r.volume_type.qos_specs # Trigger qos specs load
|
||||
return result
|
||||
|
||||
def get_snapshots(self, snapshot_id=None, snapshot_name=None,
|
||||
|
@ -129,7 +133,41 @@ class DBPersistence(persistence_base.PersistenceDriverBase):
|
|||
if not changed:
|
||||
changed = self.get_fields(volume)
|
||||
|
||||
# Create
|
||||
extra_specs = changed.pop('extra_specs', None)
|
||||
qos_specs = changed.pop('qos_specs', None)
|
||||
|
||||
# Since OVOs are not tracking QoS or Extra specs dictionary changes,
|
||||
# we only support setting QoS or Extra specs on creation or add them
|
||||
# later.
|
||||
if changed.get('volume_type_id'):
|
||||
vol_type_fields = {'id': volume.volume_type_id,
|
||||
'name': volume.volume_type_id,
|
||||
'extra_specs': extra_specs,
|
||||
'is_public': True}
|
||||
if qos_specs:
|
||||
res = self.db.qos_specs_create(objects.CONTEXT,
|
||||
{'name': volume.volume_type_id,
|
||||
'consumer': 'back-end',
|
||||
'specs': qos_specs})
|
||||
# Cinder is automatically generating an ID, replace it
|
||||
query = sqla_api.model_query(objects.CONTEXT,
|
||||
models.QualityOfServiceSpecs)
|
||||
query.filter_by(id=res['id']).update(
|
||||
{'id': volume.volume_type.qos_specs_id})
|
||||
|
||||
self.db.volume_type_create(objects.CONTEXT, vol_type_fields)
|
||||
else:
|
||||
if extra_specs is not None:
|
||||
self.db.volume_type_extra_specs_update_or_create(
|
||||
objects.CONTEXT, volume.volume_type_id, extra_specs)
|
||||
|
||||
self.db.qos_specs_update(objects.CONTEXT,
|
||||
volume.volume_type.qos_specs_id,
|
||||
{'name': volume.volume_type_id,
|
||||
'consumer': 'back-end',
|
||||
'specs': qos_specs})
|
||||
|
||||
# Create the volume
|
||||
if 'id' in changed:
|
||||
LOG.debug('set_volume creating %s', changed)
|
||||
try:
|
||||
|
@ -200,10 +238,36 @@ class DBPersistence(persistence_base.PersistenceDriverBase):
|
|||
if self.soft_deletes:
|
||||
LOG.debug('soft deleting volume %s', volume.id)
|
||||
self.db.volume_destroy(objects.CONTEXT, volume.id)
|
||||
if volume.volume_type_id:
|
||||
LOG.debug('soft deleting volume type %s',
|
||||
volume.volume_type_id)
|
||||
self.db.volume_destroy(objects.CONTEXT, volume.volume_type_id)
|
||||
if volume.volume_type.qos_specs_id:
|
||||
self.db.qos_specs_delete(objects.CONTEXT,
|
||||
volume.volume_type.qos_specs_id)
|
||||
else:
|
||||
LOG.debug('hard deleting volume %s', volume.id)
|
||||
query = sqla_api.model_query(objects.CONTEXT, models.Volume)
|
||||
query.filter_by(id=volume.id).delete()
|
||||
if volume.volume_type_id:
|
||||
LOG.debug('hard deleting volume type %s',
|
||||
volume.volume_type_id)
|
||||
query = sqla_api.model_query(objects.CONTEXT,
|
||||
models.VolumeTypeExtraSpecs)
|
||||
query.filter_by(volume_type_id=volume.volume_type_id).delete()
|
||||
|
||||
query = sqla_api.model_query(objects.CONTEXT,
|
||||
models.VolumeType)
|
||||
query.filter_by(id=volume.volume_type_id).delete()
|
||||
|
||||
query = sqla_api.model_query(objects.CONTEXT,
|
||||
models.QualityOfServiceSpecs)
|
||||
qos_id = volume.volume_type.qos_specs_id
|
||||
if qos_id:
|
||||
query.filter(sqla_api.or_(
|
||||
models.QualityOfServiceSpecs.id == qos_id,
|
||||
models.QualityOfServiceSpecs.specs_id == qos_id
|
||||
)).delete()
|
||||
super(DBPersistence, self).delete_volume(volume)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
|
|
|
@ -144,6 +144,13 @@ Some of the fields we could be interested in are:
|
|||
- `bootable`: Not relevant for *cinderlib*, but maybe useful for the
|
||||
*cinderlib* user.
|
||||
|
||||
- `extra_specs`: Extra volume configuration used by some drivers to specify
|
||||
additional information, such as compression, deduplication, etc. Key-Value
|
||||
pairs are driver specific.
|
||||
|
||||
- `qos_specs`: Backend QoS configuration. Dictionary with driver specific
|
||||
key-value pares that enforced by the backend.
|
||||
|
||||
.. note::
|
||||
|
||||
*Cinderlib* automatically generates a UUID for the `id` if one is not
|
||||
|
|
|
@ -53,14 +53,16 @@ class BasePersistenceTest(unittest2.TestCase):
|
|||
return self.create_volumes([{'size': i, 'name': 'disk%s' % i}
|
||||
for i in range(1, n+1)])
|
||||
|
||||
def create_volumes(self, data):
|
||||
def create_volumes(self, data, sort=True):
|
||||
vols = []
|
||||
for d in data:
|
||||
d.setdefault('backend_or_vol', self.backend)
|
||||
vol = cinderlib.Volume(**d)
|
||||
vols.append(vol)
|
||||
self.persistence.set_volume(vol)
|
||||
return self.sorted(vols)
|
||||
if sort:
|
||||
return self.sorted(vols)
|
||||
return vols
|
||||
|
||||
def create_snapshots(self):
|
||||
vols = self.create_n_volumes(2)
|
||||
|
@ -185,6 +187,87 @@ class BasePersistenceTest(unittest2.TestCase):
|
|||
volume_id=vols[0].id)
|
||||
self.assertListEqualObj([], res)
|
||||
|
||||
def _check_volume_type(self, extra_specs, qos_specs, vol):
|
||||
self.assertEqual(vol.id, vol.volume_type.id)
|
||||
self.assertEqual(vol.id, vol.volume_type.name)
|
||||
self.assertTrue(vol.volume_type.is_public)
|
||||
self.assertEqual(extra_specs, vol.volume_type.extra_specs)
|
||||
|
||||
if qos_specs:
|
||||
self.assertEqual(vol.id, vol.volume_type.qos_specs_id)
|
||||
self.assertEqual(vol.id, vol.volume_type.qos_specs.id)
|
||||
self.assertEqual(vol.id, vol.volume_type.qos_specs.name)
|
||||
self.assertEqual('back-end', vol.volume_type.qos_specs.consumer)
|
||||
self.assertEqual(qos_specs, vol.volume_type.qos_specs.specs)
|
||||
else:
|
||||
self.assertIsNone(vol.volume_type.qos_specs_id)
|
||||
|
||||
def test_get_volumes_extra_specs(self):
|
||||
extra_specs = [{'k1': 'v1', 'k2': 'v2'},
|
||||
{'kk1': 'vv1', 'kk2': 'vv2', 'kk3': 'vv3'}]
|
||||
vols = self.create_volumes(
|
||||
[{'size': 1, 'extra_specs': extra_specs[0]},
|
||||
{'size': 2, 'extra_specs': extra_specs[1]}],
|
||||
sort=False)
|
||||
|
||||
# Check the volume type and the extra specs on created volumes
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type(extra_specs[i], None, vols[i])
|
||||
|
||||
# Check that we get what we stored
|
||||
res = self.persistence.get_volumes(backend_name=self.backend.id)
|
||||
vols = self.sorted(vols)
|
||||
self.assertListEqualObj(vols, self.sorted(res))
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type(vols[i].volume_type.extra_specs, {},
|
||||
vols[i])
|
||||
|
||||
def test_get_volumes_qos_specs(self):
|
||||
qos_specs = [{'q1': 'r1', 'q2': 'r2'},
|
||||
{'qq1': 'rr1', 'qq2': 'rr2', 'qq3': 'rr3'}]
|
||||
vols = self.create_volumes(
|
||||
[{'size': 1, 'qos_specs': qos_specs[0]},
|
||||
{'size': 2, 'qos_specs': qos_specs[1]}],
|
||||
sort=False)
|
||||
|
||||
# Check the volume type and the extra specs on created volumes
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type({}, qos_specs[i], vols[i])
|
||||
|
||||
# Check that we get what we stored
|
||||
res = self.persistence.get_volumes(backend_name=self.backend.id)
|
||||
vols = self.sorted(vols)
|
||||
res = self.sorted(res)
|
||||
self.assertListEqualObj(vols, res)
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type({}, vols[i].volume_type.qos_specs.specs,
|
||||
vols[i])
|
||||
|
||||
def test_get_volumes_extra_and_qos_specs(self):
|
||||
qos_specs = [{'q1': 'r1', 'q2': 'r2'},
|
||||
{'qq1': 'rr1', 'qq2': 'rr2', 'qq3': 'rr3'}]
|
||||
extra_specs = [{'k1': 'v1', 'k2': 'v2'},
|
||||
{'kk1': 'vv1', 'kk2': 'vv2', 'kk3': 'vv3'}]
|
||||
vols = self.create_volumes(
|
||||
[{'size': 1, 'qos_specs': qos_specs[0],
|
||||
'extra_specs': extra_specs[0]},
|
||||
{'size': 2, 'qos_specs': qos_specs[1],
|
||||
'extra_specs': extra_specs[1]}],
|
||||
sort=False)
|
||||
|
||||
# Check the volume type and the extra specs on created volumes
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type(extra_specs[i], qos_specs[i], vols[i])
|
||||
|
||||
# Check that we get what we stored
|
||||
res = self.persistence.get_volumes(backend_name=self.backend.id)
|
||||
vols = self.sorted(vols)
|
||||
self.assertListEqualObj(vols, self.sorted(res))
|
||||
for i in range(len(vols)):
|
||||
self._check_volume_type(vols[i].volume_type.extra_specs,
|
||||
vols[i].volume_type.qos_specs.specs,
|
||||
vols[i])
|
||||
|
||||
def test_delete_volume(self):
|
||||
vols = self.create_n_volumes(2)
|
||||
self.persistence.delete_volume(vols[0])
|
||||
|
|
Loading…
Reference in New Issue