Sync volume versionedobject to ORM

The following patch syncs the volume object to match the
ORM.  More specifically, it adds metadata, admin_metdata,
volume_attachment, and volume_type to the volume object.
These fields are required to convert cinder internals to
use volume versionedobject.  It was originally part of
a large patch, but is broken up to make reviews more
bearable.

Change-Id: I80c1734072cb26acbe207914e812a3b169b69dfb
Partial-Implements: blueprint cinder-objects
This commit is contained in:
Thang Pham 2015-08-28 07:02:50 -07:00
parent 126e4f215e
commit 3af149cfef
10 changed files with 770 additions and 27 deletions

View File

@ -24,8 +24,10 @@ def register_all():
# NOTE(danms): You must make sure your object gets imported in this
# function in order for it to be registered by services that may
# need to receive it via RPC.
__import__('cinder.objects.volume')
__import__('cinder.objects.service')
__import__('cinder.objects.snapshot')
__import__('cinder.objects.backup')
__import__('cinder.objects.consistencygroup')
__import__('cinder.objects.service')
__import__('cinder.objects.snapshot')
__import__('cinder.objects.volume')
__import__('cinder.objects.volume_attachment')
__import__('cinder.objects.volume_type')

View File

@ -142,7 +142,10 @@ class CinderPersistentObject(object):
class CinderComparableObject(base.ComparableVersionedObject):
pass
def __eq__(self, obj):
if hasattr(obj, 'obj_to_primitive'):
return self.obj_to_primitive() == obj.obj_to_primitive()
return False
class ObjectListBase(base.ObjectListBase):

View File

@ -24,15 +24,18 @@ from cinder.objects import base
from cinder import utils
CONF = cfg.CONF
OPTIONAL_FIELDS = []
OPTIONAL_FIELDS = ['metadata', 'admin_metadata',
'volume_type', 'volume_attachment']
LOG = logging.getLogger(__name__)
@base.CinderObjectRegistry.register
class Volume(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat):
base.CinderObjectDictCompat, base.CinderComparableObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added metadata, admin_metadata, volume_attachment, and
# volume_type
VERSION = '1.1'
fields = {
'id': fields.UUIDField(),
@ -70,12 +73,19 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
'deleted': fields.BooleanField(default=False),
'bootable': fields.BooleanField(default=False),
'multiattach': fields.BooleanField(default=False),
'replication_status': fields.StringField(nullable=True),
'replication_extended_status': fields.StringField(nullable=True),
'replication_driver_data': fields.StringField(nullable=True),
'previous_status': fields.StringField(nullable=True),
'metadata': fields.DictOfStringsField(nullable=True),
'admin_metadata': fields.DictOfStringsField(nullable=True),
'volume_type': fields.ObjectField('VolumeType', nullable=True),
'volume_attachment': fields.ListOfObjectsField('VolumeAttachment',
nullable=True),
}
# NOTE(thangp): obj_extra_fields is used to hold properties that are not
@ -94,19 +104,80 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
def name(self):
return CONF.volume_name_template % self.name_id
def __init__(self, *args, **kwargs):
super(Volume, self).__init__(*args, **kwargs)
self._orig_metadata = {}
self._orig_admin_metadata = {}
self._reset_metadata_tracking()
def obj_reset_changes(self, fields=None):
super(Volume, self).obj_reset_changes(fields)
self._reset_metadata_tracking(fields=fields)
def _reset_metadata_tracking(self, fields=None):
if fields is None or 'metadata' in fields:
self._orig_metadata = (dict(self.metadata)
if 'metadata' in self else {})
if fields is None or 'admin_metadata' in fields:
self._orig_admin_metadata = (dict(self.admin_metadata)
if 'admin_metadata' in self
else {})
def obj_what_changed(self):
changes = super(Volume, self).obj_what_changed()
if 'metadata' in self and self.metadata != self._orig_metadata:
changes.add('metadata')
if ('admin_metadata' in self and
self.admin_metadata != self._orig_admin_metadata):
changes.add('admin_metadata')
return changes
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with a target version."""
super(Volume, self).obj_make_compatible(primitive, target_version)
target_version = utils.convert_version_to_tuple(target_version)
@staticmethod
def _from_db_object(context, volume, db_volume):
def _from_db_object(context, volume, db_volume, expected_attrs=None):
if expected_attrs is None:
expected_attrs = []
for name, field in volume.fields.items():
value = db_volume[name]
if name in OPTIONAL_FIELDS:
continue
value = db_volume.get(name)
if isinstance(field, fields.IntegerField):
value = value or 0
volume[name] = value
# Get data from db_volume object that was queried by joined query
# from DB
if 'metadata' in expected_attrs:
volume.metadata = {}
metadata = db_volume.get('volume_metadata', [])
if metadata:
volume.metadata = {item['key']: item['value']
for item in metadata}
if 'admin_metadata' in expected_attrs:
volume.admin_metadata = {}
metadata = db_volume.get('volume_admin_metadata', [])
if metadata:
volume.admin_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:
volume.volume_type = objects.VolumeType._from_db_object(
context, objects.VolumeType(), db_volume_type,
expected_attrs='extra_specs')
if 'volume_attachment' in expected_attrs:
attachments = base.obj_make_list(
context, objects.VolumeAttachmentList(context),
objects.VolumeAttachment,
db_volume.get('volume_attachment'))
volume.volume_attachment = attachments.objects
volume._context = context
volume.obj_reset_changes()
return volume
@ -114,7 +185,9 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
@base.remotable_classmethod
def get_by_id(cls, context, id):
db_volume = db.volume_get(context, id)
return cls._from_db_object(context, cls(context), db_volume)
expected_attrs = ['admin_metadata', 'metadata']
return cls._from_db_object(context, cls(context), db_volume,
expected_attrs=expected_attrs)
@base.remotable
def create(self):
@ -129,30 +202,109 @@ class Volume(base.CinderPersistentObject, base.CinderObject,
def save(self):
updates = self.cinder_obj_get_changes()
if updates:
db.volume_update(self._context, self.id, updates)
if 'metadata' in updates:
# Metadata items that are not specified in the
# self.metadata will be deleted
metadata = updates.pop('metadata', None)
self.metadata = db.volume_metadata_update(self._context,
self.id, metadata,
True)
if self._context.is_admin and 'admin_metadata' in updates:
metadata = updates.pop('admin_metadata', None)
self.admin_metadata = db.volume_admin_metadata_update(
self._context, self.id, metadata, True)
self.obj_reset_changes()
db.volume_update(self._context, self.id, updates)
self.obj_reset_changes()
@base.remotable
def destroy(self):
db.volume_destroy(self._context, self.id)
with self.obj_as_admin():
db.volume_destroy(self._context, self.id)
def obj_load_attr(self, attrname):
if attrname not in OPTIONAL_FIELDS:
raise exception.ObjectActionError(
action='obj_load_attr',
reason=_('attribute %s not lazy-loadable') % attrname)
if not self._context:
raise exception.OrphanedObjectError(method='obj_load_attr',
objtype=self.obj_name())
if attrname == 'metadata':
self.metadata = db.volume_metadata_get(self._context, self.id)
elif attrname == 'admin_metadata':
self.admin_metadata = {}
if self._context.is_admin:
self.admin_metadata = db.volume_admin_metadata_get(
self._context, self.id)
elif attrname == 'volume_type':
self.volume_type = objects.VolumeType.get_by_id(
self._context, self.volume_type_id)
elif attrname == 'volume_attachment':
attachments = (
objects.VolumeAttachmentList.get_all_by_volume_id(
self._context, self.id))
self.volume_attachment = attachments.objects
self.obj_reset_changes(fields=[attrname])
def delete_metadata_key(self, key):
db.volume_metadata_delete(self._context, self.id, key)
md_was_changed = 'metadata' in self.obj_what_changed()
del self.metadata[key]
self._orig_metadata.pop(key, None)
if not md_was_changed:
self.obj_reset_changes(['metadata'])
@base.CinderObjectRegistry.register
class VolumeList(base.ObjectListBase, base.CinderObject):
VERSION = '1.0'
VERSION = '1.1'
fields = {
'objects': fields.ListOfObjectsField('Volume'),
}
child_versions = {
'1.0': '1.0'
'1.0': '1.0',
'1.1': '1.1',
}
@base.remotable_classmethod
def get_all(cls, context, marker, limit, sort_key, sort_dir,
filters=None):
volumes = db.volume_get_all(context, marker, limit, sort_key,
sort_dir, filters=filters)
def get_all(cls, context, marker, limit, sort_keys=None, sort_dirs=None,
filters=None, offset=None):
volumes = db.volume_get_all(context, marker, limit,
sort_keys=sort_keys, sort_dirs=sort_dirs,
filters=filters, offset=offset)
expected_attrs = ['admin_metadata', 'metadata']
return base.obj_make_list(context, cls(context), objects.Volume,
volumes)
volumes, expected_attrs=expected_attrs)
@base.remotable_classmethod
def get_all_by_host(cls, context, host, filters=None):
volumes = db.volume_get_all_by_host(context, host, filters)
expected_attrs = ['admin_metadata', 'metadata']
return base.obj_make_list(context, cls(context), objects.Volume,
volumes, expected_attrs=expected_attrs)
@base.remotable_classmethod
def get_all_by_group(cls, context, group_id, filters=None):
volumes = db.volume_get_all_by_group(context, group_id, filters)
expected_attrs = ['admin_metadata', 'metadata']
return base.obj_make_list(context, cls(context), objects.Volume,
volumes, expected_attrs=expected_attrs)
@base.remotable_classmethod
def get_all_by_project(cls, context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, filters=None,
offset=None):
volumes = db.volume_get_all_by_project(context, project_id, marker,
limit, sort_keys=sort_keys,
sort_dirs=sort_dirs,
filters=filters, offset=offset)
expected_attrs = ['admin_metadata', 'metadata']
return base.obj_make_list(context, cls(context), objects.Volume,
volumes, expected_attrs=expected_attrs)

View File

@ -0,0 +1,104 @@
# Copyright 2015 SimpliVity Corp.
#
# 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.
from oslo_config import cfg
from oslo_log import log as logging
from oslo_versionedobjects import fields
from cinder import db
from cinder import objects
from cinder.objects import base
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@base.CinderObjectRegistry.register
class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat,
base.CinderComparableObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(),
'volume_id': fields.UUIDField(),
'instance_uuid': fields.UUIDField(nullable=True),
'attached_host': fields.StringField(nullable=True),
'mountpoint': fields.StringField(nullable=True),
'attach_time': fields.DateTimeField(nullable=True),
'detach_time': fields.DateTimeField(nullable=True),
'attach_status': fields.StringField(nullable=True),
'attach_mode': fields.StringField(nullable=True),
}
@staticmethod
def _from_db_object(context, attachment, db_attachment):
for name, field in attachment.fields.items():
value = db_attachment.get(name)
if isinstance(field, fields.IntegerField):
value = value or 0
attachment[name] = value
attachment._context = context
attachment.obj_reset_changes()
return attachment
@base.remotable_classmethod
def get_by_id(cls, context, id):
db_attach = db.volume_attachment_get(context, id)
return cls._from_db_object(context, cls(context), db_attach)
@base.remotable
def save(self):
updates = self.cinder_obj_get_changes()
if updates:
db.volume_attachment_update(self._context, self.id, updates)
self.obj_reset_changes()
@base.CinderObjectRegistry.register
class VolumeAttachmentList(base.ObjectListBase, base.CinderObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('VolumeAttachment'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_all_by_volume_id(cls, context, volume_id):
attachments = db.volume_attachment_get_used_by_volume_id(context,
volume_id)
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)
@base.remotable_classmethod
def get_all_by_host(cls, context, volume_id, host):
attachments = db.volume_attachment_get_by_host(context, volume_id,
host)
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)
@base.remotable_classmethod
def get_all_by_instance_uuid(cls, context, volume_id, instance_uuid):
attachments = db.volume_attachment_get_by_instance_uuid(
context, volume_id, instance_uuid)
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)

View File

@ -0,0 +1,125 @@
# Copyright 2015 SimpliVity Corp.
#
# 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.
from oslo_config import cfg
from oslo_log import log as logging
from oslo_versionedobjects import fields
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.objects import base
from cinder.volume import volume_types
CONF = cfg.CONF
OPTIONAL_FIELDS = ['extra_specs', 'projects']
LOG = logging.getLogger(__name__)
@base.CinderObjectRegistry.register
class VolumeType(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat, base.CinderComparableObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(nullable=True),
'description': fields.StringField(nullable=True),
'is_public': fields.BooleanField(default=True),
'projects': fields.ListOfStringsField(nullable=True),
'extra_specs': fields.DictOfStringsField(nullable=True),
}
@staticmethod
def _from_db_object(context, type, db_type, expected_attrs=None):
if expected_attrs is None:
expected_attrs = []
for name, field in type.fields.items():
if name in OPTIONAL_FIELDS:
continue
value = db_type[name]
if isinstance(field, fields.IntegerField):
value = value or 0
type[name] = value
# Get data from db_type object that was queried by joined query
# from DB
if 'extra_specs' in expected_attrs:
type.extra_specs = {}
specs = db_type.get('extra_specs')
if specs and isinstance(specs, list):
type.extra_specs = {item['key']: item['value']
for item in specs}
elif specs and isinstance(specs, dict):
type.extra_specs = specs
if 'projects' in expected_attrs:
type.projects = db_type.get('projects', [])
type._context = context
type.obj_reset_changes()
return type
@base.remotable_classmethod
def get_by_id(cls, context, id):
db_volume_type = volume_types.get_volume_type(
context, id, expected_fields=['extra_specs', 'projects'])
expected_attrs = ['extra_specs', 'projects']
return cls._from_db_object(context, cls(context), db_volume_type,
expected_attrs=expected_attrs)
@base.remotable
def create(self):
if self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='create',
reason=_('already created'))
db_volume_type = volume_types.create(self._context, self.name,
self.extra_specs,
self.is_public, self.projects,
self.description)
self._from_db_object(self._context, self, db_volume_type)
@base.remotable
def save(self):
updates = self.cinder_obj_get_changes()
if updates:
volume_types.update(self._context, self.id, self.name,
self.description)
self.obj_reset_changes()
@base.remotable
def destroy(self):
with self.obj_as_admin():
volume_types.destroy(self._context, self.id)
@base.CinderObjectRegistry.register
class VolumeTypeList(base.ObjectListBase, base.CinderObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('VolumeType'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_all(cls, context, inactive=0, search_opts=None):
types = volume_types.get_all_types(context, inactive, search_opts)
expected_attrs = ['extra_specs', 'projects']
return base.obj_make_list(context, cls(context),
objects.VolumeType, types,
expected_attrs=expected_attrs)

View File

@ -25,7 +25,12 @@ def fake_db_volume(**updates):
'availability_zone': 'fake_availability_zone',
'status': 'available',
'attach_status': 'detached',
'previous_status': None
'previous_status': None,
'metadata': {},
'admin_metadata': {},
'volume_attachment': [],
'volume_metadata': [],
'volume_admin_metadata': [],
}
for name, field in objects.Volume.fields.items():
@ -36,7 +41,7 @@ def fake_db_volume(**updates):
elif field.default != fields.UnspecifiedDefault:
db_volume[name] = field.default
else:
raise Exception('fake_db_volume needs help with %s' % name)
raise Exception('fake_db_volume needs help with %s.' % name)
if updates:
db_volume.update(updates)
@ -44,6 +49,70 @@ def fake_db_volume(**updates):
return db_volume
def fake_db_volume_type(**updates):
db_volume_type = {
'id': '1',
'name': 'type-1',
'description': 'A fake volume type',
'is_public': True,
'projects': [],
'extra_specs': {},
}
for name, field in objects.VolumeType.fields.items():
if name in db_volume_type:
continue
if field.nullable:
db_volume_type[name] = None
elif field.default != fields.UnspecifiedDefault:
db_volume_type[name] = field.default
else:
raise Exception('fake_db_volume_type needs help with %s.' % name)
if updates:
db_volume_type.update(updates)
return db_volume_type
def fake_db_volume_attachment(**updates):
db_volume_attachment = {
'id': '1',
'volume_id': '1',
}
for name, field in objects.VolumeAttachment.fields.items():
if name in db_volume_attachment:
continue
if field.nullable:
db_volume_attachment[name] = None
elif field.default != fields.UnspecifiedDefault:
db_volume_attachment[name] = field.default
else:
raise Exception(
'fake_db_volume_attachment needs help with %s.' % name)
if updates:
db_volume_attachment.update(updates)
return db_volume_attachment
def fake_volume_obj(context, **updates):
return objects.Volume._from_db_object(context, objects.Volume(),
fake_db_volume(**updates))
expected_attrs = updates.pop('expected_attrs',
['metadata', 'admin_metadata'])
vol = objects.Volume._from_db_object(context, objects.Volume(),
fake_db_volume(**updates),
expected_attrs=expected_attrs)
return vol
def fake_volume_type_obj(context, **updates):
return objects.VolumeType._from_db_object(
context, objects.VolumeType(), fake_db_volume_type(**updates))
def fake_volume_attachment_obj(context, **updates):
return objects.VolumeAttachment._from_db_object(
context, objects.VolumeAttachment(),
fake_db_volume_attachment(**updates))

View File

@ -16,6 +16,7 @@ import datetime
import uuid
from iso8601 import iso8601
from oslo_versionedobjects import fields
from cinder.objects import base
from cinder.tests.unit import objects as test_objects
@ -75,3 +76,24 @@ class TestCinderObject(test_objects.BaseObjectsTestCase):
self.obj.scheduled_at = now_tz
self.assertDictEqual({'scheduled_at': now},
self.obj.cinder_obj_get_changes())
class TestCinderComparableObject(test_objects.BaseObjectsTestCase):
def test_comparable_objects(self):
@base.CinderObjectRegistry.register
class MyComparableObj(base.CinderObject,
base.CinderObjectDictCompat,
base.CinderComparableObject):
fields = {'foo': fields.Field(fields.Integer())}
class NonVersionedObject(object):
pass
obj1 = MyComparableObj(foo=1)
obj2 = MyComparableObj(foo=1)
obj3 = MyComparableObj(foo=2)
obj4 = NonVersionedObject()
self.assertTrue(obj1 == obj2)
self.assertFalse(obj1 == obj3)
self.assertFalse(obj1 == obj4)
self.assertNotEqual(obj1, None)

View File

@ -14,19 +14,32 @@
import mock
from cinder import context
from cinder import objects
from cinder.tests.unit import fake_volume
from cinder.tests.unit import objects as test_objects
class TestVolume(test_objects.BaseObjectsTestCase):
def setUp(self):
super(TestVolume, self).setUp()
# NOTE (e0ne): base tests contains original RequestContext from
# oslo_context. We change it to our RequestContext implementation
# to have 'elevated' method
self.context = context.RequestContext(self.user_id, self.project_id,
is_admin=False)
@staticmethod
def _compare(test, db, obj):
for field, value in db.items():
if not hasattr(obj, field):
continue
test.assertEqual(db[field], obj[field])
@mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
@mock.patch('cinder.db.volume_get')
def test_get_by_id(self, volume_get):
def test_get_by_id(self, volume_get, volume_glance_metadata_get):
db_volume = fake_volume.fake_db_volume()
volume_get.return_value = db_volume
volume = objects.Volume.get_by_id(self.context, 1)
@ -50,13 +63,56 @@ class TestVolume(test_objects.BaseObjectsTestCase):
volume_update.assert_called_once_with(self.context, volume.id,
{'display_name': 'foobar'})
@mock.patch('cinder.db.volume_metadata_update',
return_value={'key1': 'value1'})
@mock.patch('cinder.db.volume_update')
def test_save_with_metadata(self, volume_update, metadata_update):
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.display_name = 'foobar'
volume.metadata = {'key1': 'value1'}
self.assertEqual({'display_name': 'foobar',
'metadata': {'key1': 'value1'}},
volume.obj_get_changes())
volume.save()
volume_update.assert_called_once_with(self.context, volume.id,
{'display_name': 'foobar'})
metadata_update.assert_called_once_with(self.context, volume.id,
{'key1': 'value1'}, True)
@mock.patch('cinder.db.volume_admin_metadata_update',
return_value={'key1': 'value1'})
@mock.patch('cinder.db.volume_update')
def test_save_with_admin_metadata(self, volume_update,
admin_metadata_update):
# Test with no admin context
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.admin_metadata = {'key1': 'value1'}
volume.save()
self.assertFalse(admin_metadata_update.called)
# Test with admin context
admin_context = context.RequestContext(self.user_id, self.project_id,
is_admin=True)
volume = objects.Volume._from_db_object(admin_context,
objects.Volume(), db_volume)
volume.admin_metadata = {'key1': 'value1'}
volume.save()
admin_metadata_update.assert_called_once_with(
admin_context, volume.id, {'key1': 'value1'}, True)
@mock.patch('cinder.db.volume_destroy')
def test_destroy(self, volume_destroy):
db_volume = fake_volume.fake_db_volume()
volume = objects.Volume._from_db_object(self.context,
objects.Volume(), db_volume)
volume.destroy()
volume_destroy.assert_called_once_with(self.context, '1')
self.assertTrue(volume_destroy.called)
admin_context = volume_destroy.call_args[0][0]
self.assertTrue(admin_context.is_admin)
def test_obj_fields(self):
volume = objects.Volume(context=self.context, id=2, _name_id=2)
@ -69,10 +125,20 @@ class TestVolume(test_objects.BaseObjectsTestCase):
previous_status='backing-up')
self.assertEqual('backing-up', volume.previous_status)
@mock.patch('cinder.db.volume_metadata_delete')
def test_delete_metadata_key(self, metadata_delete):
volume = objects.Volume(self.context, id=1)
volume.metadata = {'key1': 'value1', 'key2': 'value2'}
self.assertEqual({}, volume._orig_metadata)
volume.delete_metadata_key('key2')
self.assertEqual({'key1': 'value1'}, volume.metadata)
metadata_delete.assert_called_once_with(self.context, '1', 'key2')
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):
def test_get_all(self, volume_get_all, volume_glance_metadata_get):
db_volume = fake_volume.fake_db_volume()
volume_get_all.return_value = [db_volume]
@ -83,3 +149,35 @@ class TestVolumeList(test_objects.BaseObjectsTestCase):
mock.sentinel.sort_dir)
self.assertEqual(1, len(volumes))
TestVolume._compare(self, db_volume, volumes[0])
@mock.patch('cinder.db.volume_get_all_by_host')
def test_get_by_host(self, get_all_by_host):
db_volume = fake_volume.fake_db_volume()
get_all_by_host.return_value = [db_volume]
volumes = objects.VolumeList.get_all_by_host(
self.context, 'fake-host')
self.assertEqual(1, len(volumes))
TestVolume._compare(self, db_volume, volumes[0])
@mock.patch('cinder.db.volume_get_all_by_group')
def test_get_by_group(self, get_all_by_group):
db_volume = fake_volume.fake_db_volume()
get_all_by_group.return_value = [db_volume]
volumes = objects.VolumeList.get_all_by_group(
self.context, 'fake-host')
self.assertEqual(1, len(volumes))
TestVolume._compare(self, db_volume, volumes[0])
@mock.patch('cinder.db.volume_get_all_by_project')
def test_get_by_project(self, get_all_by_project):
db_volume = fake_volume.fake_db_volume()
get_all_by_project.return_value = [db_volume]
volumes = objects.VolumeList.get_all_by_project(
self.context, mock.sentinel.project_id, mock.sentinel.marker,
mock.sentinel.limit, mock.sentinel.sorted_keys,
mock.sentinel.sorted_dirs, mock.sentinel.filters)
self.assertEqual(1, len(volumes))
TestVolume._compare(self, db_volume, volumes[0])

View File

@ -0,0 +1,73 @@
# Copyright 2015 SimpliVity Corp.
#
# 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.
import mock
from cinder import objects
from cinder.tests.unit import fake_volume
from cinder.tests.unit import objects as test_objects
class TestVolumeAttachment(test_objects.BaseObjectsTestCase):
@staticmethod
def _compare(test, db, obj):
for field, value in db.items():
test.assertEqual(db[field], obj[field])
@mock.patch('cinder.db.volume_attachment_get')
def test_get_by_id(self, volume_attachment_get):
db_attachment = fake_volume.fake_db_volume_attachment()
volume_attachment_get.return_value = db_attachment
attachment = objects.VolumeAttachment.get_by_id(self.context, '1')
self._compare(self, db_attachment, attachment)
@mock.patch('cinder.db.volume_attachment_update')
def test_save(self, volume_attachment_update):
attachment = fake_volume.fake_volume_attachment_obj(self.context)
attachment.attach_status = 'attaching'
attachment.save()
volume_attachment_update.assert_called_once_with(
self.context, attachment.id, {'attach_status': 'attaching'})
class TestVolumeAttachmentList(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.volume_attachment_get_used_by_volume_id')
def test_get_all_by_volume_id(self, get_used_by_volume_id):
db_attachment = fake_volume.fake_db_volume_attachment()
get_used_by_volume_id.return_value = [db_attachment]
attachments = objects.VolumeAttachmentList.get_all_by_volume_id(
self.context, mock.sentinel.volume_id)
self.assertEqual(1, len(attachments))
TestVolumeAttachment._compare(self, db_attachment, attachments[0])
@mock.patch('cinder.db.volume_attachment_get_by_host')
def test_get_all_by_host(self, get_by_host):
db_attachment = fake_volume.fake_db_volume_attachment()
get_by_host.return_value = [db_attachment]
attachments = objects.VolumeAttachmentList.get_all_by_host(
self.context, mock.sentinel.volume_id, mock.sentinel.host)
self.assertEqual(1, len(attachments))
TestVolumeAttachment._compare(self, db_attachment, attachments[0])
@mock.patch('cinder.db.volume_attachment_get_by_instance_uuid')
def test_get_all_by_instance_uuid(self, get_by_instance_uuid):
db_attachment = fake_volume.fake_db_volume_attachment()
get_by_instance_uuid.return_value = [db_attachment]
attachments = objects.VolumeAttachmentList.get_all_by_instance_uuid(
self.context, mock.sentinel.volume_id, mock.sentinel.uuid)
self.assertEqual(1, len(attachments))
TestVolumeAttachment._compare(self, db_attachment, attachments[0])

View File

@ -0,0 +1,95 @@
# Copyright 2015 SimpliVity Corp.
#
# 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.
import mock
from cinder import context
from cinder import objects
from cinder.tests.unit import fake_volume
from cinder.tests.unit import objects as test_objects
class TestVolumeType(test_objects.BaseObjectsTestCase):
def setUp(self):
super(TestVolumeType, self).setUp()
# NOTE (e0ne): base tests contains original RequestContext from
# oslo_context. We change it to our RequestContext implementation
# to have 'elevated' method
self.context = context.RequestContext(self.user_id, self.project_id,
is_admin=False)
@staticmethod
def _compare(test, db, obj):
for field, value in db.items():
test.assertEqual(db[field], obj[field])
@mock.patch('cinder.db.volume_type_get')
def test_get_by_id(self, volume_type_get):
db_volume_type = fake_volume.fake_db_volume_type()
volume_type_get.return_value = db_volume_type
volume_type = objects.VolumeType.get_by_id(self.context, '1')
self._compare(self, db_volume_type, volume_type)
@mock.patch('cinder.volume.volume_types.create')
def test_create(self, volume_type_create):
db_volume_type = fake_volume.fake_db_volume_type()
volume_type_create.return_value = db_volume_type
volume_type = objects.VolumeType(context=self.context)
volume_type.name = db_volume_type['name']
volume_type.extra_specs = db_volume_type['extra_specs']
volume_type.is_public = db_volume_type['is_public']
volume_type.projects = db_volume_type['projects']
volume_type.description = db_volume_type['description']
volume_type.create()
volume_type_create.assert_called_once_with(
self.context, db_volume_type['name'],
db_volume_type['extra_specs'], db_volume_type['is_public'],
db_volume_type['projects'], db_volume_type['description'])
@mock.patch('cinder.volume.volume_types.update')
def test_save(self, volume_type_update):
db_volume_type = fake_volume.fake_db_volume_type()
volume_type = objects.VolumeType._from_db_object(self.context,
objects.VolumeType(),
db_volume_type)
volume_type.description = 'foobar'
volume_type.save()
volume_type_update.assert_called_once_with(self.context,
volume_type.id,
volume_type.name,
volume_type.description)
@mock.patch('cinder.volume.volume_types.destroy')
def test_destroy(self, volume_type_destroy):
db_volume_type = fake_volume.fake_db_volume_type()
volume_type = objects.VolumeType._from_db_object(self.context,
objects.VolumeType(),
db_volume_type)
volume_type.destroy()
self.assertTrue(volume_type_destroy.called)
admin_context = volume_type_destroy.call_args[0][0]
self.assertTrue(admin_context.is_admin)
class TestVolumeTypeList(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.volume.volume_types.get_all_types')
def test_get_all(self, get_all_types):
db_volume_type = fake_volume.fake_db_volume_type()
get_all_types.return_value = [db_volume_type]
volume_types = objects.VolumeTypeList.get_all(self.context)
self.assertEqual(1, len(volume_types))
TestVolumeType._compare(self, db_volume_type, volume_types[0])