# 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')
'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
super(Volume, self).__init__(*args, **kwargs)
self._orig_metadata = {}
self._orig_admin_metadata = {}
+ self._orig_glance_metadata = {}
self._reset_metadata_tracking()
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()
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
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:
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()
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)
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
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)
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])
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():
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': {},
'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():
'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',
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)
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()
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())
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
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]]
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]
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)