Fix: Snapshot creation with volume types

This patch corrects the functionality where a resource with
'volume_type_id' field is assumed to have a 'volume_type' field
which isn't the case for snapshots.
This patch also adds minimal test coverage for get_changed_fields
method.

Closes-Bug: #1836724

Change-Id: If7816bf3deb37c526b86b0baed500bebe637bd40
This commit is contained in:
whoami-rajat 2019-07-15 22:41:49 +05:30
parent c44afc95af
commit d6bec909e0
4 changed files with 163 additions and 96 deletions

View File

@ -103,7 +103,7 @@ 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 getattr(resource._ovo, 'volume_type', 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

View File

@ -13,108 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinder.cmd import volume as volume_cmd
from cinder.db.sqlalchemy import api
from cinder.db.sqlalchemy import models
from cinder import objects
from cinder.objects import base as cinder_base_ovo
from oslo_versionedobjects import fields
import cinderlib
from cinderlib.tests.unit import base
from cinderlib.tests.unit.persistence import helper
from cinderlib.tests.unit import utils
class BasePersistenceTest(base.BaseTest):
@classmethod
def setUpClass(cls):
# Save OVO methods that some persistence plugins mess up
cls.ovo_methods = {}
for ovo_name in cinder_base_ovo.CinderObjectRegistry.obj_classes():
ovo_cls = getattr(objects, ovo_name)
cls.ovo_methods[ovo_name] = {
'save': getattr(ovo_cls, 'save', None),
'get_by_id': getattr(ovo_cls, 'get_by_id', None),
}
cls.original_impl = volume_cmd.session.IMPL
cinderlib.Backend.global_initialization = False
cinderlib.setup(persistence_config=cls.PERSISTENCE_CFG)
@classmethod
def tearDownClass(cls):
volume_cmd.session.IMPL = cls.original_impl
cinderlib.Backend.global_initialization = False
api.main_context_manager = api.enginefacade.transaction_context()
for ovo_name, methods in cls.ovo_methods.items():
ovo_cls = getattr(objects, ovo_name)
for method_name, method in methods.items():
if method:
setattr(ovo_cls, method_name, method)
class BasePersistenceTest(helper.TestHelper):
def setUp(self):
super(BasePersistenceTest, self).setUp()
self.context = cinderlib.objects.CONTEXT
def sorted(self, resources, key='id'):
return sorted(resources, key=lambda x: getattr(x, key))
def create_n_volumes(self, n):
return self.create_volumes([{'size': i, 'name': 'disk%s' % i}
for i in range(1, n + 1)])
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)
if sort:
return self.sorted(vols)
return vols
def create_snapshots(self):
vols = self.create_n_volumes(2)
snaps = []
for i, vol in enumerate(vols):
snap = cinderlib.Snapshot(vol, name='snaps%s' % (i + i))
snaps.append(snap)
self.persistence.set_snapshot(snap)
return self.sorted(snaps)
def create_connections(self):
vols = self.create_n_volumes(2)
conns = []
for i, vol in enumerate(vols):
conn = cinderlib.Connection(self.backend, volume=vol,
connection_info={'conn': {'data': {}}})
conns.append(conn)
self.persistence.set_connection(conn)
return self.sorted(conns)
def create_key_values(self):
kvs = []
for i in range(2):
kv = cinderlib.KeyValue(key='key%i' % i, value='value%i' % i)
kvs.append(kv)
self.persistence.set_key_value(kv)
return kvs
def _convert_to_dict(self, obj):
if isinstance(obj, models.BASE):
return dict(obj)
if not isinstance(obj, cinderlib.objects.Object):
return obj
res = dict(obj._ovo)
for key, value in obj._ovo.fields.items():
if isinstance(value, fields.ObjectField):
res.pop(key, None)
res.pop('glance_metadata', None)
res.pop('metadata', None)
return res
def assertListEqualObj(self, expected, actual):
exp = [self._convert_to_dict(e) for e in expected]

View File

@ -0,0 +1,117 @@
# Copyright (c) 2018, Red Hat, Inc.
# All Rights Reserved.
#
# 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 cinder.cmd import volume as volume_cmd
from cinder.db.sqlalchemy import api
from cinder.db.sqlalchemy import models
from cinder import objects
from cinder.objects import base as cinder_base_ovo
from oslo_versionedobjects import fields
import cinderlib
from cinderlib.tests.unit import base
class TestHelper(base.BaseTest):
@classmethod
def setUpClass(cls):
# Save OVO methods that some persistence plugins mess up
cls.ovo_methods = {}
for ovo_name in cinder_base_ovo.CinderObjectRegistry.obj_classes():
ovo_cls = getattr(objects, ovo_name)
cls.ovo_methods[ovo_name] = {
'save': getattr(ovo_cls, 'save', None),
'get_by_id': getattr(ovo_cls, 'get_by_id', None),
}
cls.original_impl = volume_cmd.session.IMPL
cinderlib.Backend.global_initialization = False
cinderlib.setup(persistence_config=cls.PERSISTENCE_CFG)
@classmethod
def tearDownClass(cls):
volume_cmd.session.IMPL = cls.original_impl
cinderlib.Backend.global_initialization = False
api.main_context_manager = api.enginefacade.transaction_context()
for ovo_name, methods in cls.ovo_methods.items():
ovo_cls = getattr(objects, ovo_name)
for method_name, method in methods.items():
if method:
setattr(ovo_cls, method_name, method)
def setUp(self):
super(TestHelper, self).setUp()
self.context = cinderlib.objects.CONTEXT
def sorted(self, resources, key='id'):
return sorted(resources, key=lambda x: getattr(x, key))
def create_n_volumes(self, n):
return self.create_volumes([{'size': i, 'name': 'disk%s' % i}
for i in range(1, n + 1)])
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)
if sort:
return self.sorted(vols)
return vols
def create_snapshots(self):
vols = self.create_n_volumes(2)
snaps = []
for i, vol in enumerate(vols):
snap = cinderlib.Snapshot(vol, name='snaps%s' % (i + i))
snaps.append(snap)
self.persistence.set_snapshot(snap)
return self.sorted(snaps)
def create_connections(self):
vols = self.create_n_volumes(2)
conns = []
for i, vol in enumerate(vols):
conn = cinderlib.Connection(self.backend, volume=vol,
connection_info={'conn': {'data': {}}})
conns.append(conn)
self.persistence.set_connection(conn)
return self.sorted(conns)
def create_key_values(self):
kvs = []
for i in range(2):
kv = cinderlib.KeyValue(key='key%i' % i, value='value%i' % i)
kvs.append(kv)
self.persistence.set_key_value(kv)
return kvs
def _convert_to_dict(self, obj):
if isinstance(obj, models.BASE):
return dict(obj)
if not isinstance(obj, cinderlib.objects.Object):
return obj
res = dict(obj._ovo)
for key, value in obj._ovo.fields.items():
if isinstance(value, fields.ObjectField):
res.pop(key, None)
res.pop('glance_metadata', None)
res.pop('metadata', None)
return res

View File

@ -0,0 +1,43 @@
# Copyright (c) 2018, Red Hat, Inc.
# All Rights Reserved.
#
# 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 cinderlib
from cinderlib.tests.unit.persistence import helper
class TestBasePersistence(helper.TestHelper):
PERSISTENCE_CFG = {'storage': 'memory'}
def tearDown(self):
self.persistence.volumes.clear()
self.persistence.snapshots.clear()
self.persistence.connections.clear()
self.persistence.key_values.clear()
super(TestBasePersistence, self).tearDown()
def test_get_changed_fields_volume(self):
vol = cinderlib.Volume(self.backend, size=1, extra_specs={'k': 'v'})
self.persistence.set_volume(vol)
vol._ovo.display_name = "abcde"
result = self.persistence.get_changed_fields(vol)
self.assertEqual(result, {'display_name': vol._ovo.display_name})
def test_get_changed_fields_snapshot(self):
vol = cinderlib.Volume(self.backend, size=1, extra_specs={'k': 'v'})
snap = cinderlib.Snapshot(vol)
self.persistence.set_snapshot(snap)
snap._ovo.display_name = "abcde"
result = self.persistence.get_changed_fields(snap)
self.assertEqual(result, {'display_name': snap._ovo.display_name})