Merge "Add some missing fields to Volume object"

This commit is contained in:
Jenkins 2015-12-02 02:38:11 +00:00 committed by Gerrit Code Review
commit cea85c26be
7 changed files with 184 additions and 35 deletions

View File

@ -33,10 +33,12 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
# Version 1.0: Initial version
# Version 1.1: Added metadata, admin_metadata, volume_attachment, and
# volume_type
VERSION = '1.1'
# Version 1.2: Added glance_metadata, consistencygroup and snapshots
VERSION = '1.2'
OPTIONAL_FIELDS = ('metadata', 'admin_metadata',
'volume_type', 'volume_attachment')
OPTIONAL_FIELDS = ('metadata', 'admin_metadata', 'glance_metadata',
'volume_type', 'volume_attachment', 'consistencygroup',
'snapshots')
DEFAULT_EXPECTED_ATTR = ('admin_metadata', 'metadata')
@ -86,9 +88,13 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
'metadata': fields.DictOfStringsField(nullable=True),
'admin_metadata': fields.DictOfStringsField(nullable=True),
'glance_metadata': fields.DictOfStringsField(nullable=True),
'volume_type': fields.ObjectField('VolumeType', nullable=True),
'volume_attachment': fields.ObjectField('VolumeAttachmentList',
nullable=True),
'consistencygroup': fields.ObjectField('ConsistencyGroup',
nullable=True),
'snapshots': fields.ObjectField('SnapshotList', nullable=True),
}
# NOTE(thangp): obj_extra_fields is used to hold properties that are not
@ -111,6 +117,7 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
super(Volume, self).__init__(*args, **kwargs)
self._orig_metadata = {}
self._orig_admin_metadata = {}
self._orig_glance_metadata = {}
self._reset_metadata_tracking()
@ -126,6 +133,10 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
self._orig_admin_metadata = (dict(self.admin_metadata)
if 'admin_metadata' in self
else {})
if fields is None or 'glance_metadata' in fields:
self._orig_glance_metadata = (dict(self.glance_metadata)
if 'glance_metadata' in self
else {})
def obj_what_changed(self):
changes = super(Volume, self).obj_what_changed()
@ -134,6 +145,9 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
if ('admin_metadata' in self and
self.admin_metadata != self._orig_admin_metadata):
changes.add('admin_metadata')
if ('glance_metadata' in self and
self.glance_metadata != self._orig_glance_metadata):
changes.add('glance_metadata')
return changes
@ -168,6 +182,12 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
if metadata:
volume.admin_metadata = {item['key']: item['value']
for item in metadata}
if 'glance_metadata' in expected_attrs:
volume.glance_metadata = {}
metadata = db_volume.get('volume_glance_metadata', [])
if metadata:
volume.glance_metadata = {item['key']: item['value']
for item in metadata}
if 'volume_type' in expected_attrs:
db_volume_type = db_volume.get('volume_type')
if db_volume_type:
@ -180,6 +200,18 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
objects.VolumeAttachment,
db_volume.get('volume_attachment'))
volume.volume_attachment = attachments
if 'consistencygroup' in expected_attrs:
consistencygroup = objects.ConsistencyGroup(context)
consistencygroup._from_db_object(context,
consistencygroup,
db_volume['consistencygroup'])
volume.consistencygroup = consistencygroup
if 'snapshots' in expected_attrs:
snapshots = base.obj_make_list(
context, objects.SnapshotList(context),
objects.Snapshot,
db_volume['snapshots'])
volume.snapshots = snapshots
volume._context = context
volume.obj_reset_changes()
@ -191,6 +223,17 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
raise exception.ObjectActionError(action='create',
reason=_('already created'))
updates = self.cinder_obj_get_changes()
if 'consistencygroup' in updates:
raise exception.ObjectActionError(
action='create', reason=_('consistencygroup assigned'))
if 'glance_metadata' in updates:
raise exception.ObjectActionError(
action='create', reason=_('glance_metadata assigned'))
if 'snapshots' in updates:
raise exception.ObjectActionError(
action='create', reason=_('snapshots assigned'))
db_volume = db.volume_create(self._context, updates)
self._from_db_object(self._context, self, db_volume)
@ -198,6 +241,15 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
def save(self):
updates = self.cinder_obj_get_changes()
if updates:
if 'consistencygroup' in updates:
raise exception.ObjectActionError(
action='save', reason=_('consistencygroup changed'))
if 'glance_metadata' in updates:
raise exception.ObjectActionError(
action='save', reason=_('glance_metadata changed'))
if 'snapshots' in updates:
raise exception.ObjectActionError(
action='save', reason=_('snapshots changed'))
if 'metadata' in updates:
# Metadata items that are not specified in the
# self.metadata will be deleted
@ -234,6 +286,9 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
if self._context.is_admin:
self.admin_metadata = db.volume_admin_metadata_get(
self._context, self.id)
elif attrname == 'glance_metadata':
self.glance_metadata = db.volume_glance_metadata_get(
self._context, self.id)
elif attrname == 'volume_type':
self.volume_type = objects.VolumeType.get_by_id(
self._context, self.volume_type_id)
@ -241,6 +296,13 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
attachments = objects.VolumeAttachmentList.get_all_by_volume_id(
self._context, self.id)
self.volume_attachment = attachments
elif attrname == 'consistencygroup':
consistencygroup = objects.ConsistencyGroup.get_by_id(
self._context, self.consistencygroup_id)
self.consistencygroup = consistencygroup
elif attrname == 'snapshots':
self.snapshots = objects.SnapshotList.get_all_for_volume(
self._context, self.id)
self.obj_reset_changes(fields=[attrname])

View File

@ -19,9 +19,9 @@ from cinder import objects
def fake_db_consistencygroup(**updates):
db_values = {
'id': 1,
'user_id': 2,
'project_id': 3,
'id': '1',
'user_id': '2',
'project_id': '3',
'host': 'FakeHost',
}
for name, field in objects.ConsistencyGroup.fields.items():

View File

@ -19,11 +19,11 @@ from cinder.objects import snapshot
def fake_db_snapshot(**updates):
db_snapshot = {
'id': 1,
'id': '1',
'volume_id': 'fake_id',
'status': "creating",
'progress': '0%',
'volume_size': 1,
'volume_size': '1',
'display_name': 'fake_name',
'display_description': 'fake_description',
'metadata': {},

View File

@ -26,11 +26,11 @@ def fake_db_volume(**updates):
'status': 'available',
'attach_status': 'detached',
'previous_status': None,
'metadata': {},
'admin_metadata': {},
'volume_attachment': [],
'volume_metadata': [],
'volume_admin_metadata': [],
'volume_glance_metadata': [],
'snapshots': [],
}
for name, field in objects.Volume.fields.items():

View File

@ -32,7 +32,7 @@ object_data = {
'ServiceList': '1.0-d242d3384b68e5a5a534e090ff1d5161',
'Snapshot': '1.0-a6c33eefeadefb324d79f72f66c54e9a',
'SnapshotList': '1.0-71661e7180ef6cc51501704a9bea4bf1',
'Volume': '1.1-6037de222b09c330b33b419a0c1b10c6',
'Volume': '1.2-97c3977846dae6588381e7bd3e6e6558',
'VolumeAttachment': '1.0-f14a7c03ffc5b93701d496251a5263aa',
'VolumeAttachmentList': '1.0-307d2b6c8dd55ef854f6386898e9e98e',
'VolumeList': '1.1-03ba6cb8c546683e64e15c50042cb1a3',

View File

@ -17,15 +17,21 @@ import mock
from cinder import context
from cinder import exception
from cinder import objects
from cinder.tests.unit import fake_consistencygroup
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit import objects as test_objects
class TestVolume(test_objects.BaseObjectsTestCase):
@staticmethod
def _compare(test, db, obj):
db = {k: v for k, v in db.items()
if not k.endswith('metadata') or k.startswith('volume')}
test_objects.BaseObjectsTestCase._compare(test, db, obj)
@mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
@mock.patch('cinder.db.sqlalchemy.api.volume_get')
def test_get_by_id(self, volume_get, volume_glance_metadata_get):
def test_get_by_id(self, volume_get):
db_volume = fake_volume.fake_db_volume()
volume_get.return_value = db_volume
volume = objects.Volume.get_by_id(self.context, 1)
@ -98,6 +104,30 @@ class TestVolume(test_objects.BaseObjectsTestCase):
admin_metadata_update.assert_called_once_with(
admin_context, volume.id, {'key1': 'value1'}, True)
def test_save_with_glance_metadata(self):
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.display_name = 'foobar'
volume.glance_metadata = {'key1': 'value1'}
self.assertRaises(exception.ObjectActionError, volume.save)
def test_save_with_consistencygroup(self):
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.display_name = 'foobar'
volume.consistencygroup = objects.ConsistencyGroup()
self.assertRaises(exception.ObjectActionError, volume.save)
def test_save_with_snapshots(self):
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.display_name = 'foobar'
volume.snapshots = objects.SnapshotList()
self.assertRaises(exception.ObjectActionError, volume.save)
@mock.patch('cinder.db.volume_destroy')
def test_destroy(self, volume_destroy):
db_volume = fake_volume.fake_db_volume()
@ -129,12 +159,17 @@ class TestVolume(test_objects.BaseObjectsTestCase):
metadata_delete.assert_called_once_with(self.context, '1', 'key2')
@mock.patch('cinder.db.volume_metadata_get')
@mock.patch('cinder.db.volume_glance_metadata_get')
@mock.patch('cinder.db.volume_admin_metadata_get')
@mock.patch('cinder.objects.volume_type.VolumeType.get_by_id')
@mock.patch('cinder.objects.volume_attachment.VolumeAttachmentList.'
'get_all_by_volume_id')
def test_obj_load_attr(self, mock_va_get_all_by_vol, mock_vt_get_by_id,
mock_admin_metadata_get, mock_metadata_get):
@mock.patch('cinder.objects.consistencygroup.ConsistencyGroup.get_by_id')
@mock.patch('cinder.objects.snapshot.SnapshotList.get_all_for_volume')
def test_obj_load_attr(self, mock_sl_get_all_for_volume, mock_cg_get_by_id,
mock_va_get_all_by_vol, mock_vt_get_by_id,
mock_admin_metadata_get, mock_glance_metadata_get,
mock_metadata_get):
volume = objects.Volume._from_db_object(
self.context, objects.Volume(), fake_volume.fake_db_volume())
@ -144,6 +179,13 @@ class TestVolume(test_objects.BaseObjectsTestCase):
self.assertEqual(metadata, volume.metadata)
mock_metadata_get.assert_called_once_with(self.context, volume.id)
# Test glance_metadata lazy-loaded field
glance_metadata = {'foo': 'bar'}
mock_glance_metadata_get.return_value = glance_metadata
self.assertEqual(glance_metadata, volume.glance_metadata)
mock_glance_metadata_get.assert_called_once_with(
self.context, volume.id)
# Test volume_type lazy-loaded field
volume_type = objects.VolumeType(context=self.context, id=5)
mock_vt_get_by_id.return_value = volume_type
@ -151,6 +193,20 @@ class TestVolume(test_objects.BaseObjectsTestCase):
mock_vt_get_by_id.assert_called_once_with(self.context,
volume.volume_type_id)
# Test consistencygroup lazy-loaded field
consistencygroup = objects.ConsistencyGroup(context=self.context, id=2)
mock_cg_get_by_id.return_value = consistencygroup
self.assertEqual(consistencygroup, volume.consistencygroup)
mock_cg_get_by_id.assert_called_once_with(self.context,
volume.consistencygroup_id)
# Test snapshots lazy-loaded field
snapshots = objects.SnapshotList(context=self.context, id=2)
mock_sl_get_all_for_volume.return_value = snapshots
self.assertEqual(snapshots, volume.snapshots)
mock_sl_get_all_for_volume.assert_called_once_with(self.context,
volume.id)
# Test volume_attachment lazy-loaded field
va_objs = [objects.VolumeAttachment(context=self.context, id=i)
for i in [3, 4, 5]]
@ -175,11 +231,43 @@ class TestVolume(test_objects.BaseObjectsTestCase):
self.assertEqual(adm_metadata, volume.admin_metadata)
mock_admin_metadata_get.assert_called_once_with(adm_context, volume.id)
def test_from_db_object_with_all_expected_attributes(self):
expected_attrs = ['metadata', 'admin_metadata', 'glance_metadata',
'volume_type', 'volume_attachment',
'consistencygroup']
db_metadata = [{'key': 'foo', 'value': 'bar'}]
db_admin_metadata = [{'key': 'admin_foo', 'value': 'admin_bar'}]
db_glance_metadata = [{'key': 'glance_foo', 'value': 'glance_bar'}]
db_volume_type = fake_volume.fake_db_volume_type()
db_volume_attachments = fake_volume.fake_db_volume_attachment()
db_consistencygroup = fake_consistencygroup.fake_db_consistencygroup()
db_snapshots = fake_snapshot.fake_db_snapshot()
db_volume = fake_volume.fake_db_volume(
volume_metadata=db_metadata,
volume_admin_metadata=db_admin_metadata,
volume_glance_metadata=db_glance_metadata,
volume_type=db_volume_type,
volume_attachment=[db_volume_attachments],
consistencygroup=db_consistencygroup,
snapshots=[db_snapshots],
)
volume = objects.Volume._from_db_object(self.context, objects.Volume(),
db_volume, expected_attrs)
self.assertEqual({'foo': 'bar'}, volume.metadata)
self.assertEqual({'admin_foo': 'admin_bar'}, volume.admin_metadata)
self.assertEqual({'glance_foo': 'glance_bar'}, volume.glance_metadata)
self._compare(self, db_volume_type, volume.volume_type)
self._compare(self, db_volume_attachments, volume.volume_attachment)
self._compare(self, db_consistencygroup, volume.consistencygroup)
self._compare(self, db_snapshots, volume.snapshots)
class TestVolumeList(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
@mock.patch('cinder.db.volume_get_all')
def test_get_all(self, volume_get_all, volume_glance_metadata_get):
def test_get_all(self, volume_get_all):
db_volume = fake_volume.fake_db_volume()
volume_get_all.return_value = [db_volume]

View File

@ -1600,26 +1600,25 @@ class VolumeManager(manager.SchedulerDependentManager):
rpcapi = volume_rpcapi.VolumeAPI()
# Create new volume on remote host
new_vol_values = dict(volume)
del new_vol_values['id']
del new_vol_values['_name_id']
new_vol_values.pop('name', None)
# We don't copy volume_type because the db sets that according to
# volume_type_id, which we do copy
new_vol_values.pop('volume_type', None)
skip = {'id', '_name_id', 'name', 'host', 'status',
'attach_status', 'migration_status', 'volume_type',
'consistencygroup', 'volume_attachment'}
# We don't copy volume_type, consistencygroup and volume_attachment,
# because the db sets that according to [field]_id, which we do copy.
# We also skip some other values that are either set manually later or
# during creation of Volume object.
new_vol_values = {k: volume[k] for k in set(volume.keys()) - skip}
if new_type_id:
new_vol_values['volume_type_id'] = new_type_id
new_vol_values['host'] = host['host']
new_vol_values['status'] = 'creating'
# FIXME(jdg): using a : delimeter is confusing to
# me below here. We're adding a string member to a dict
# using a :, which is kind of a poor choice in this case
# I think
new_vol_values['migration_status'] = 'target:%s' % volume['id']
new_vol_values['attach_status'] = 'detached'
new_vol_values.pop('volume_attachment', None)
new_volume = objects.Volume(context=ctxt, **new_vol_values)
new_volume = objects.Volume(
context=ctxt,
host=host['host'],
status='creating',
attach_status='detached',
migration_status='target:%s' % volume['id'],
**new_vol_values
)
new_volume.create()
rpcapi.create_volume(ctxt, new_volume, host['host'],
None, None, allow_reschedule=False)