From bf0ad013ba7a601760cff7732c3ae5bd0c5efc15 Mon Sep 17 00:00:00 2001 From: Edward Hope-Morley Date: Wed, 5 Feb 2014 17:42:53 +0000 Subject: [PATCH] RBD unit test improvements This commit brings the implementation of the test_rbd and test_backup_ceph unit tests more inline and tries to ensure that no mock is set without being unset so as not to tread on the toes of any other tests. Change-Id: Ibb6bd3bf90952914512e3fa39fd216f43f0f8ecb Closes-bug: 1276657 --- cinder/tests/test_backup_ceph.py | 167 +++--- cinder/tests/test_rbd.py | 862 +++++++++++++++++-------------- 2 files changed, 539 insertions(+), 490 deletions(-) diff --git a/cinder/tests/test_backup_ceph.py b/cinder/tests/test_backup_ceph.py index ae76ef949..2545519c6 100644 --- a/cinder/tests/test_backup_ceph.py +++ b/cinder/tests/test_backup_ceph.py @@ -14,14 +14,10 @@ # under the License. """ Tests for Ceph backup service.""" -import eventlet -import fcntl import hashlib import mock import os -import subprocess import tempfile -import time import uuid from cinder.backup.drivers import ceph @@ -36,58 +32,24 @@ from cinder.volume.drivers import rbd as rbddriver LOG = logging.getLogger(__name__) +# This is used to collect raised exceptions so that tests may check what was +# raised. +# NOTE: this must be initialised in test setUp(). +RAISED_EXCEPTIONS = [] -class ImageNotFound(Exception): - _called = False - def __init__(self, *args, **kwargs): - self.__class__._called = True - - @classmethod - def called(cls): - ret = cls._called - cls._called = False - return ret - - -class ImageBusy(Exception): - _called = False +class MockException(Exception): def __init__(self, *args, **kwargs): - self.__class__._called = True - - @classmethod - def called(cls): - ret = cls._called - cls._called = False - return ret - - -def common_backup_mocks(f): - """Decorator to set mocks common to all backup tests. - """ - def _common_backup_mocks_inner(inst, *args, **kwargs): - inst.service.rbd.Image.size = mock.Mock() - inst.service.rbd.Image.size.return_value = \ - inst.chunk_size * inst.num_chunks + RAISED_EXCEPTIONS.append(self.__class__) - with mock.patch.object(inst.service, '_get_rbd_support') as \ - mock_rbd_support: - mock_rbd_support.return_value = (True, 3) - with mock.patch.object(inst.service, 'get_backup_snaps'): - return f(inst, *args, **kwargs) - return _common_backup_mocks_inner +class MockImageNotFoundException(MockException): + """Used as mock for rbd.ImageNotFound.""" -def common_restore_mocks(f): - """Decorator to set mocks common to all restore tests. - """ - @common_backup_mocks - def _common_restore_mocks_inner(inst, *args, **kwargs): - return f(inst, *args, **kwargs) - - return _common_restore_mocks_inner +class MockImageBusyException(MockException): + """Used as mock for rbd.ImageBusy.""" def common_mocks(f): @@ -97,9 +59,20 @@ def common_mocks(f): mocks that can't/dont't get unset. """ def _common_inner_inner1(inst, *args, **kwargs): + # NOTE(dosaboy): mock Popen to, by default, raise Exception in order to + # ensure that any test ending up in a subprocess fails + # if not properly mocked. + @mock.patch('subprocess.Popen') + # NOTE(dosaboy): mock out eventlet.sleep() so that it does nothing. + @mock.patch('eventlet.sleep') + @mock.patch('time.time') @mock.patch('cinder.backup.drivers.ceph.rbd') @mock.patch('cinder.backup.drivers.ceph.rados') - def _common_inner_inner2(mock_rados, mock_rbd): + def _common_inner_inner2(mock_rados, mock_rbd, mock_time, mock_sleep, + mock_popen): + mock_time.side_effect = inst.time_inc + mock_popen.side_effect = Exception + inst.mock_rados = mock_rados inst.mock_rbd = mock_rbd inst.mock_rados.Rados = mock.Mock @@ -107,23 +80,15 @@ def common_mocks(f): inst.mock_rbd.RBD = mock.Mock inst.mock_rbd.Image = mock.Mock inst.mock_rbd.Image.close = mock.Mock() - inst.mock_rbd.ImageBusy = ImageBusy - inst.mock_rbd.ImageNotFound = ImageNotFound + inst.mock_rbd.ImageBusy = MockImageBusyException + inst.mock_rbd.ImageNotFound = MockImageNotFoundException inst.service.rbd = inst.mock_rbd inst.service.rados = inst.mock_rados - - with mock.patch.object(time, 'time') as mock_time: - mock_time.side_effect = inst.time_inc - with mock.patch.object(eventlet, 'sleep'): - # Mock Popen to raise Exception in order to ensure that any - # test ending up in a subprocess fails if not properly - # mocked. - with mock.patch.object(subprocess, 'Popen') as mock_popen: - mock_popen.side_effect = Exception - return f(inst, *args, **kwargs) + return f(inst, *args, **kwargs) return _common_inner_inner2() + return _common_inner_inner1 @@ -171,6 +136,8 @@ class BackupCephTestCase(test.TestCase): mock_popen.side_effect = MockPopen def setUp(self): + global RAISED_EXCEPTIONS + RAISED_EXCEPTIONS = [] super(BackupCephTestCase, self).setUp() self.ctxt = context.get_admin_context() @@ -303,7 +270,6 @@ class BackupCephTestCase(test.TestCase): self.assertEqual(len(snaps), 3) @common_mocks - @common_backup_mocks def test_transfer_data_from_rbd_to_file(self): self.mock_rbd.Image.read = mock.Mock() self.mock_rbd.Image.read.return_value = \ @@ -358,7 +324,6 @@ class BackupCephTestCase(test.TestCase): self.assertEqual(checksum.digest(), self.checksum.digest()) @common_mocks - @common_backup_mocks def test_transfer_data_from_file_to_rbd(self): def mock_write_data(data, offset): @@ -380,7 +345,6 @@ class BackupCephTestCase(test.TestCase): self.assertEqual(checksum.digest(), self.checksum.digest()) @common_mocks - @common_backup_mocks def test_transfer_data_from_file_to_file(self): with tempfile.NamedTemporaryFile() as test_file: self.volume_file.seek(0) @@ -398,8 +362,8 @@ class BackupCephTestCase(test.TestCase): self.assertEqual(checksum.digest(), self.checksum.digest()) @common_mocks - @common_backup_mocks def test_backup_volume_from_file(self): + checksum = hashlib.sha256() def mock_write_data(data, offset): checksum.update(data) @@ -410,13 +374,13 @@ class BackupCephTestCase(test.TestCase): with mock.patch.object(self.service, '_discard_bytes'): with tempfile.NamedTemporaryFile() as test_file: - checksum = hashlib.sha256() - self.service.backup(self.backup, self.volume_file) # Ensure the files are equal self.assertEqual(checksum.digest(), self.checksum.digest()) + self.assertTrue(self.service.rbd.Image.write.called) + @common_mocks def test_get_backup_base_name(self): name = self.service._get_backup_base_name(self.volume_id, @@ -432,9 +396,9 @@ class BackupCephTestCase(test.TestCase): "volume-%s.backup.%s" % (self.volume_id, '1234')) @common_mocks - @common_backup_mocks + @mock.patch('fcntl.fcntl') @mock.patch('subprocess.Popen') - def test_backup_volume_from_rbd(self, mock_popen): + def test_backup_volume_from_rbd(self, mock_popen, mock_fnctl): backup_name = self.service._get_backup_base_name(self.backup_id, diff_format=True) @@ -457,8 +421,10 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.RBD.list = mock.Mock() self.mock_rbd.RBD.list.return_value = [backup_name] - with mock.patch.object(fcntl, 'fcntl'): - with mock.patch.object(self.service, '_discard_bytes'): + with mock.patch.object(self.service, 'get_backup_snaps') as \ + mock_get_backup_snaps: + with mock.patch.object(self.service, '_full_backup') as \ + mock_full_backup: with mock.patch.object(self.service, '_try_delete_base_image'): with tempfile.NamedTemporaryFile() as test_file: checksum = hashlib.sha256() @@ -467,9 +433,8 @@ class BackupCephTestCase(test.TestCase): 'pool_foo', 'user_foo', 'conf_foo') - rbd_io = rbddriver.RBDImageIOWrapper(meta) - - self.service.backup(self.backup, rbd_io) + self.service.backup(self.backup, + rbddriver.RBDImageIOWrapper(meta)) self.assertEqual(self.callstack, ['popen_init', 'read', @@ -478,12 +443,14 @@ class BackupCephTestCase(test.TestCase): 'stdout_close', 'communicate']) + self.assertFalse(mock_full_backup.called) + self.assertTrue(mock_get_backup_snaps.called) + # Ensure the files are equal self.assertEqual(checksum.digest(), self.checksum.digest()) @common_mocks - @common_backup_mocks def test_backup_vol_length_0(self): volume_id = str(uuid.uuid4()) self._create_volume_db_entry(volume_id, 0) @@ -497,7 +464,6 @@ class BackupCephTestCase(test.TestCase): backup, self.volume_file) @common_mocks - @common_restore_mocks def test_restore(self): backup_name = self.service._get_backup_base_name(self.backup_id, diff_format=True) @@ -511,7 +477,8 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.Image.read = mock.Mock() self.mock_rbd.Image.read.side_effect = mock_read_data - with mock.patch.object(self.service, '_discard_bytes'): + with mock.patch.object(self.service, '_discard_bytes') as \ + mock_discard_bytes: with tempfile.NamedTemporaryFile() as test_file: self.volume_file.seek(0) @@ -525,6 +492,10 @@ class BackupCephTestCase(test.TestCase): # Ensure the files are equal self.assertEqual(checksum.digest(), self.checksum.digest()) + self.assertTrue(mock_discard_bytes.called) + + self.assertTrue(self.service.rbd.Image.read.called) + @common_mocks def test_discard_bytes(self): self.mock_rbd.Image.discard = mock.Mock() @@ -540,6 +511,7 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.Image.write = mock.Mock() self.mock_rbd.Image.flush = mock.Mock() + # Test discard with no remainder with mock.patch.object(self.service, '_file_is_rbd') as \ mock_file_is_rbd: mock_file_is_rbd.return_value = False @@ -553,7 +525,9 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.Image.write.reset_mock() self.mock_rbd.Image.flush.reset_mock() + self.mock_rbd.Image.discard.reset_mock() + # Now test with a remainder. with mock.patch.object(self.service, '_file_is_rbd') as \ mock_file_is_rbd: mock_file_is_rbd.return_value = False @@ -631,20 +605,22 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.RBD.remove = mock.Mock() self.mock_rbd.RBD.remove.side_effect = self.mock_rbd.ImageBusy - with mock.patch.object(self.service, 'get_backup_snaps'): + with mock.patch.object(self.service, 'get_backup_snaps') as \ + mock_get_backup_snaps: self.assertRaises(self.mock_rbd.ImageBusy, self.service._try_delete_base_image, self.backup['id'], self.backup['volume_id']) + self.assertTrue(mock_get_backup_snaps.called) - self.assertTrue(self.mock_rbd.RBD.list.called) - self.assertTrue(self.mock_rbd.RBD.remove.called) - self.assertTrue(self.mock_rbd.ImageBusy.called()) + self.assertTrue(self.mock_rbd.RBD.list.called) + self.assertTrue(self.mock_rbd.RBD.remove.called) + self.assertTrue(MockImageBusyException in RAISED_EXCEPTIONS) @common_mocks def test_delete(self): with mock.patch.object(self.service, '_try_delete_base_image'): self.service.delete(self.backup) - self.assertFalse(self.mock_rbd.ImageNotFound.called()) + self.assertEqual(RAISED_EXCEPTIONS, []) @common_mocks def test_delete_image_not_found(self): @@ -653,7 +629,7 @@ class BackupCephTestCase(test.TestCase): mock_del_base.side_effect = self.mock_rbd.ImageNotFound # ImageNotFound exception is caught so that db entry can be cleared self.service.delete(self.backup) - self.assertTrue(self.mock_rbd.ImageNotFound.called()) + self.assertEqual(RAISED_EXCEPTIONS, [MockImageNotFoundException]) @common_mocks def test_diff_restore_allowed_true(self): @@ -665,15 +641,16 @@ class BackupCephTestCase(test.TestCase): self.mock_rbd.Image.size = mock.Mock() self.mock_rbd.Image.size.return_value = self.volume_size * units.GiB - mpo = mock.patch.object - with mpo(self.service, '_get_restore_point') as mock_restore_point: + with mock.patch.object(self.service, '_get_restore_point') as \ + mock_restore_point: mock_restore_point.return_value = restore_point - with mpo(self.service, '_rbd_has_extents') as mock_rbd_has_extents: + with mock.patch.object(self.service, '_rbd_has_extents') as \ + mock_rbd_has_extents: mock_rbd_has_extents.return_value = False - with mpo(self.service, '_rbd_image_exists') as \ + with mock.patch.object(self.service, '_rbd_image_exists') as \ mock_rbd_image_exists: mock_rbd_image_exists.return_value = (True, 'foo') - with mpo(self.service, '_file_is_rbd') as \ + with mock.patch.object(self.service, '_file_is_rbd') as \ mock_file_is_rbd: mock_file_is_rbd.return_value = True @@ -761,11 +738,11 @@ class BackupCephTestCase(test.TestCase): self.assertTrue(mock_rbd_has_extents.called) @common_mocks + @mock.patch('fcntl.fcntl') @mock.patch('subprocess.Popen') - def test_piped_execute(self, mock_popen): - with mock.patch.object(fcntl, 'fcntl') as mock_fcntl: - mock_fcntl.return_value = 0 - self._setup_mock_popen(mock_popen, ['out', 'err']) - self.service._piped_execute(['foo'], ['bar']) - self.assertEqual(self.callstack, ['popen_init', 'popen_init', - 'stdout_close', 'communicate']) + def test_piped_execute(self, mock_popen, mock_fcntl): + mock_fcntl.return_value = 0 + self._setup_mock_popen(mock_popen, ['out', 'err']) + self.service._piped_execute(['foo'], ['bar']) + self.assertEqual(self.callstack, ['popen_init', 'popen_init', + 'stdout_close', 'communicate']) diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py index 4c80863f4..c9baa64a6 100644 --- a/cinder/tests/test_rbd.py +++ b/cinder/tests/test_rbd.py @@ -37,6 +37,62 @@ from cinder.volume.flows.manager import create_volume LOG = logging.getLogger(__name__) +# This is used to collect raised exceptions so that tests may check what was +# raised. +# NOTE: this must be initialised in test setUp(). +RAISED_EXCEPTIONS = [] + + +class MockException(Exception): + + def __init__(self, *args, **kwargs): + RAISED_EXCEPTIONS.append(self.__class__) + + +class MockImageNotFoundException(MockException): + """Used as mock for rbd.ImageNotFound.""" + + +class MockImageBusyException(MockException): + """Used as mock for rbd.ImageBusy.""" + + +def common_mocks(f): + """Decorator to set mocks common to all tests. + + The point of doing these mocks here is so that we don't accidentally set + mocks that can't/dont't get unset. + """ + def _common_inner_inner1(inst, *args, **kwargs): + @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') + @mock.patch('cinder.volume.drivers.rbd.RADOSClient') + @mock.patch('cinder.backup.drivers.ceph.rbd') + @mock.patch('cinder.backup.drivers.ceph.rados') + def _common_inner_inner2(mock_rados, mock_rbd, mock_client, + mock_proxy): + inst.mock_rbd = mock_rbd + inst.mock_rados = mock_rados + inst.mock_client = mock_client + inst.mock_proxy = mock_proxy + inst.mock_rados.Rados = mock.Mock + inst.mock_rados.Rados.ioctx = mock.Mock() + inst.mock_rbd.RBD = mock.Mock + inst.mock_rbd.Image = mock.Mock + inst.mock_rbd.Image.close = mock.Mock() + inst.mock_rbd.RBD.Error = Exception + inst.mock_rados.Error = Exception + inst.mock_rbd.ImageBusy = MockImageBusyException + inst.mock_rbd.ImageNotFound = MockImageNotFoundException + + inst.driver.rbd = inst.mock_rbd + inst.driver.rados = inst.mock_rados + return f(inst, *args, **kwargs) + + return _common_inner_inner2() + + return _common_inner_inner1 + + CEPH_MON_DUMP = """dumped monmap epoch 1 { "epoch": 1, "fsid": "33630410-6d93-4d66-8e42-3b953cf194aa", @@ -77,6 +133,8 @@ class TestUtil(test.TestCase): class RBDTestCase(test.TestCase): def setUp(self): + global RAISED_EXCEPTIONS + RAISED_EXCEPTIONS = [] super(RBDTestCase, self).setUp() self.cfg = mock.Mock(spec=conf.Configuration) @@ -87,21 +145,11 @@ class RBDTestCase(test.TestCase): self.cfg.rbd_user = None self.cfg.volume_dd_blocksize = '1M' - # set some top level mocks for these common modules and tests can then - # set method/attributes as required. - self.rados = mock.Mock() - self.rbd = mock.Mock() - self.rbd.RBD = mock.Mock - self.rbd.Image = mock.Mock - self.rbd.ImageSnapshot = mock.Mock - mock_exec = mock.Mock() mock_exec.return_value = ('', '') self.driver = driver.RBDDriver(execute=mock_exec, - configuration=self.cfg, - rados=self.rados, - rbd=self.rbd) + configuration=self.cfg) self.driver.set_initialized() self.volume_name = u'volume-00000001' @@ -114,53 +162,51 @@ class RBDTestCase(test.TestCase): def tearDown(self): super(RBDTestCase, self).tearDown() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_create_volume(self, mock_client): - client = mock_client.return_value + @common_mocks + def test_create_volume(self): + client = self.mock_client.return_value client.__enter__.return_value = client - self.driver._supports_layering = mock.Mock() - self.driver._supports_layering.return_value = True - self.rbd.RBD.create = mock.Mock() - - self.driver.create_volume(self.volume) - - args = [client.ioctx, str(self.volume_name), - self.volume_size * units.GiB] - kwargs = {'old_format': False, - 'features': self.rbd.RBD_FEATURE_LAYERING} - - self.rbd.RBD.create.assert_called_once() - client.__enter__.assert_called_once() - client.__exit__.assert_called_once() - self.driver._supports_layering.assert_called_once() - self.rbd.RBD.create.assert_called_once_with(*args, **kwargs) - - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_create_volume_no_layering(self, mock_client): - client = mock_client.return_value + with mock.patch.object(self.driver, '_supports_layering') as \ + mock_supports_layering: + mock_supports_layering.return_value = True + self.mock_rbd.RBD.create = mock.Mock() + + self.driver.create_volume(self.volume) + + args = [client.ioctx, str(self.volume_name), + self.volume_size * units.GiB] + kwargs = {'old_format': False, + 'features': self.mock_rbd.RBD_FEATURE_LAYERING} + self.mock_rbd.RBD.create.assert_called_once_with(*args, **kwargs) + client.__enter__.assert_called_once() + client.__exit__.assert_called_once() + mock_supports_layering.assert_called_once() + + @common_mocks + def test_create_volume_no_layering(self): + client = self.mock_client.return_value client.__enter__.return_value = client - self.driver._supports_layering = mock.Mock() - self.driver._supports_layering.return_value = False - self.rbd.RBD.create = mock.Mock() - - self.driver.create_volume(self.volume) + with mock.patch.object(self.driver, '_supports_layering') as \ + mock_supports_layering: + mock_supports_layering.return_value = False + self.mock_rbd.RBD.create = mock.Mock() - args = [client.ioctx, str(self.volume_name), - self.volume_size * units.GiB] - kwargs = {'old_format': True, - 'features': 0} + self.driver.create_volume(self.volume) - self.rbd.RBD.create.assert_called_once() - client.__enter__.assert_called_once() - client.__exit__.assert_called_once() - self.driver._supports_layering.assert_called_once() - self.rbd.RBD.create.assert_called_once_with(*args, **kwargs) + args = [client.ioctx, str(self.volume_name), + self.volume_size * units.GiB] + kwargs = {'old_format': True, + 'features': 0} + self.mock_rbd.RBD.create.assert_called_once_with(*args, **kwargs) + client.__enter__.assert_called_once() + client.__exit__.assert_called_once() + mock_supports_layering.assert_called_once() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_delete_volume(self, mock_client): - client = mock_client.return_value + @common_mocks + def test_delete_volume(self): + client = self.mock_client.return_value self.driver.rbd.Image.list_snaps = mock.Mock() self.driver.rbd.Image.list_snaps.return_value = [] @@ -168,60 +214,62 @@ class RBDTestCase(test.TestCase): self.driver.rbd.Image.remove = mock.Mock() self.driver.rbd.Image.unprotect_snap = mock.Mock() - self.driver._get_clone_info = mock.Mock() - self.driver._get_clone_info.return_value = (None, None, None) - self.driver._delete_backup_snaps = mock.Mock() - - self.driver.delete_volume(self.volume) - - self.driver._get_clone_info.assert_called_once() - self.driver.rbd.Image.list_snaps.assert_called_once() - client.__enter__.assert_called_once() - client.__exit__.assert_called_once() - self.driver._delete_backup_snaps.assert_called_once() - self.assertFalse(self.driver.rbd.Image.unprotect_snap.called) - self.driver.rbd.RBD.remove.assert_called_once() - - @mock.patch('cinder.volume.drivers.rbd.rbd') - def test_delete_volume_not_found(self, mock_rbd): - mock_rbd.RBD = mock.Mock - mock_rbd.ImageNotFound = Exception - mock_rbd.Image.side_effect = mock_rbd.ImageNotFound - - self.driver.rbd = mock_rbd - - with mock.patch.object(driver, 'RADOSClient'): - self.assertIsNone(self.driver.delete_volume(self.volume)) - mock_rbd.Image.assert_called_once() - + with mock.patch.object(self.driver, '_get_clone_info') as \ + mock_get_clone_info: + with mock.patch.object(self.driver, '_delete_backup_snaps') as \ + mock_delete_backup_snaps: + mock_get_clone_info.return_value = (None, None, None) + + self.driver.delete_volume(self.volume) + + mock_get_clone_info.assert_called_once() + self.driver.rbd.Image.list_snaps.assert_called_once() + client.__enter__.assert_called_once() + client.__exit__.assert_called_once() + mock_delete_backup_snaps.assert_called_once() + self.assertFalse(self.driver.rbd.Image.unprotect_snap.called) + self.driver.rbd.RBD.remove.assert_called_once() + + @common_mocks + def delete_volume_not_found(self): + self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound + self.assertIsNone(self.driver.delete_volume(self.volume)) + self.mock_rbd.Image.assert_called_once() + # Make sure the exception was raised + self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound]) + + @common_mocks def test_delete_busy_volume(self): - self.rbd.Image.close = mock.Mock() - self.rbd.Image.list_snaps = mock.Mock() - self.rbd.Image.list_snaps.return_value = [] - self.rbd.Image.unprotect_snap = mock.Mock() - - self.rbd.ImageBusy = Exception - self.rbd.RBD.remove = mock.Mock() - self.rbd.RBD.remove.side_effect = self.rbd.ImageBusy - - self.driver._get_clone_info = mock.Mock() - self.driver._get_clone_info.return_value = (None, None, None) - self.driver._delete_backup_snaps = mock.Mock() - - with mock.patch.object(driver, 'RADOSClient') as mock_rados_client: - self.assertRaises(exception.VolumeIsBusy, - self.driver.delete_volume, self.volume) - - self.driver._get_clone_info.assert_called_once() - self.rbd.Image.list_snaps.assert_called_once() - mock_rados_client.assert_called_once() - self.driver._delete_backup_snaps.assert_called_once() - self.assertFalse(self.rbd.Image.unprotect_snap.called) - self.rbd.RBD.remove.assert_called_once() - - @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') - def test_create_snapshot(self, mock_proxy): - proxy = mock_proxy.return_value + self.mock_rbd.Image.list_snaps = mock.Mock() + self.mock_rbd.Image.list_snaps.return_value = [] + self.mock_rbd.Image.unprotect_snap = mock.Mock() + + self.mock_rbd.RBD.remove = mock.Mock() + self.mock_rbd.RBD.remove.side_effect = self.mock_rbd.ImageBusy + + with mock.patch.object(self.driver, '_get_clone_info') as \ + mock_get_clone_info: + mock_get_clone_info.return_value = (None, None, None) + with mock.patch.object(self.driver, '_delete_backup_snaps') as \ + mock_delete_backup_snaps: + with mock.patch.object(driver, 'RADOSClient') as \ + mock_rados_client: + self.assertRaises(exception.VolumeIsBusy, + self.driver.delete_volume, self.volume) + + mock_get_clone_info.assert_called_once() + self.mock_rbd.Image.list_snaps.assert_called_once() + mock_rados_client.assert_called_once() + mock_delete_backup_snaps.assert_called_once() + self.assertFalse(self.mock_rbd.Image.unprotect_snap.called) + self.mock_rbd.RBD.remove.assert_called_once() + # Make sure the exception was raised + self.assertEqual(RAISED_EXCEPTIONS, + [self.mock_rbd.ImageBusy]) + + @common_mocks + def test_create_snapshot(self): + proxy = self.mock_proxy.return_value proxy.__enter__.return_value = proxy self.driver.create_snapshot(self.snapshot) @@ -230,9 +278,9 @@ class RBDTestCase(test.TestCase): proxy.create_snap.assert_called_with(*args) proxy.protect_snap.assert_called_with(*args) - @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') - def test_delete_snapshot(self, mock_proxy): - proxy = mock_proxy.return_value + @common_mocks + def test_delete_snapshot(self): + proxy = self.mock_proxy.return_value proxy.__enter__.return_value = proxy self.driver.delete_snapshot(self.snapshot) @@ -241,9 +289,9 @@ class RBDTestCase(test.TestCase): proxy.remove_snap.assert_called_with(*args) proxy.unprotect_snap.assert_called_with(*args) + @common_mocks def test_get_clone_info(self): - - volume = self.rbd.Image() + volume = self.mock_rbd.Image() volume.set_snap = mock.Mock() volume.parent_info = mock.Mock() parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_name)) @@ -256,15 +304,15 @@ class RBDTestCase(test.TestCase): self.assertFalse(volume.set_snap.called) volume.parent_info.assert_called_once() + @common_mocks def test_get_clone_info_w_snap(self): - - volume = self.rbd.Image() + volume = self.mock_rbd.Image() volume.set_snap = mock.Mock() volume.parent_info = mock.Mock() parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_name)) volume.parent_info.return_value = parent_info - snapshot = self.rbd.ImageSnapshot() + snapshot = self.mock_rbd.ImageSnapshot() info = self.driver._get_clone_info(volume, self.volume_name, snap=snapshot) @@ -275,16 +323,14 @@ class RBDTestCase(test.TestCase): self.assertEqual(volume.set_snap.call_count, 2) volume.parent_info.assert_called_once() + @common_mocks def test_get_clone_info_w_exception(self): - - self.rbd.ImageNotFound = Exception - - volume = self.rbd.Image() + volume = self.mock_rbd.Image() volume.set_snap = mock.Mock() volume.parent_info = mock.Mock() - volume.parent_info.side_effect = self.rbd.ImageNotFound + volume.parent_info.side_effect = self.mock_rbd.ImageNotFound - snapshot = self.rbd.ImageSnapshot() + snapshot = self.mock_rbd.ImageSnapshot() info = self.driver._get_clone_info(volume, self.volume_name, snap=snapshot) @@ -294,10 +340,12 @@ class RBDTestCase(test.TestCase): volume.set_snap.assert_called_once() self.assertEqual(volume.set_snap.call_count, 2) volume.parent_info.assert_called_once() + # Make sure the exception was raised + self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound]) + @common_mocks def test_get_clone_info_deleted_volume(self): - - volume = self.rbd.Image() + volume = self.mock_rbd.Image() volume.set_snap = mock.Mock() volume.parent_info = mock.Mock() parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_name)) @@ -311,92 +359,101 @@ class RBDTestCase(test.TestCase): self.assertFalse(volume.set_snap.called) volume.parent_info.assert_called_once() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_create_cloned_volume(self, mock_client): + @common_mocks + def test_create_cloned_volume(self): src_name = u'volume-00000001' dst_name = u'volume-00000002' self.cfg.rbd_max_clone_depth = 2 - self.rbd.RBD.clone = mock.Mock() - self.driver._get_clone_depth = mock.Mock() - # Try with no flatten required - self.driver._get_clone_depth.return_value = 1 + self.mock_rbd.RBD.clone = mock.Mock() - self.rbd.Image.create_snap = mock.Mock() - self.rbd.Image.protect_snap = mock.Mock() - self.rbd.Image.close = mock.Mock() + with mock.patch.object(self.driver, '_get_clone_depth') as \ + mock_get_clone_depth: + # Try with no flatten required + mock_get_clone_depth.return_value = 1 - self.driver.create_cloned_volume(dict(name=dst_name), - dict(name=src_name)) + self.mock_rbd.Image.create_snap = mock.Mock() + self.mock_rbd.Image.protect_snap = mock.Mock() + self.mock_rbd.Image.close = mock.Mock() - self.rbd.Image.create_snap.assert_called_once() - self.rbd.Image.protect_snap.assert_called_once() - self.rbd.RBD.clone.assert_called_once() - self.rbd.Image.close.assert_called_once() + self.driver.create_cloned_volume(dict(name=dst_name), + dict(name=src_name)) - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_create_cloned_volume_w_flatten(self, mock_client): + self.mock_rbd.Image.create_snap.assert_called_once() + self.mock_rbd.Image.protect_snap.assert_called_once() + self.mock_rbd.RBD.clone.assert_called_once() + self.mock_rbd.Image.close.assert_called_once() + self.assertTrue(mock_get_clone_depth.called) + + @common_mocks + def test_create_cloned_volume_w_flatten(self): src_name = u'volume-00000001' dst_name = u'volume-00000002' self.cfg.rbd_max_clone_depth = 1 - self.rbd.RBD.Error = Exception - self.rbd.RBD.clone = mock.Mock() - self.rbd.RBD.clone.side_effect = self.rbd.RBD.Error - self.driver._get_clone_depth = mock.Mock() - # Try with no flatten required - self.driver._get_clone_depth.return_value = 1 - - self.rbd.Image.create_snap = mock.Mock() - self.rbd.Image.protect_snap = mock.Mock() - self.rbd.Image.unprotect_snap = mock.Mock() - self.rbd.Image.remove_snap = mock.Mock() - self.rbd.Image.close = mock.Mock() - - self.assertRaises(self.rbd.RBD.Error, self.driver.create_cloned_volume, - dict(name=dst_name), dict(name=src_name)) - - self.rbd.Image.create_snap.assert_called_once() - self.rbd.Image.protect_snap.assert_called_once() - self.rbd.RBD.clone.assert_called_once() - self.rbd.Image.unprotect_snap.assert_called_once() - self.rbd.Image.remove_snap.assert_called_once() - self.rbd.Image.close.assert_called_once() - - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_create_cloned_volume_w_clone_exception(self, mock_client): + self.mock_rbd.RBD.clone = mock.Mock() + self.mock_rbd.RBD.clone.side_effect = self.mock_rbd.RBD.Error + + with mock.patch.object(self.driver, '_get_clone_depth') as \ + mock_get_clone_depth: + # Try with no flatten required + mock_get_clone_depth.return_value = 1 + + self.mock_rbd.Image.create_snap = mock.Mock() + self.mock_rbd.Image.protect_snap = mock.Mock() + self.mock_rbd.Image.unprotect_snap = mock.Mock() + self.mock_rbd.Image.remove_snap = mock.Mock() + self.mock_rbd.Image.close = mock.Mock() + + self.assertRaises(self.mock_rbd.RBD.Error, + self.driver.create_cloned_volume, + dict(name=dst_name), dict(name=src_name)) + + self.mock_rbd.Image.create_snap.assert_called_once() + self.mock_rbd.Image.protect_snap.assert_called_once() + self.mock_rbd.RBD.clone.assert_called_once() + self.mock_rbd.Image.unprotect_snap.assert_called_once() + self.mock_rbd.Image.remove_snap.assert_called_once() + self.mock_rbd.Image.close.assert_called_once() + self.assertTrue(mock_get_clone_depth.called) + + @common_mocks + def test_create_cloned_volume_w_clone_exception(self): src_name = u'volume-00000001' dst_name = u'volume-00000002' self.cfg.rbd_max_clone_depth = 2 - self.rbd.RBD.Error = Exception - self.rbd.RBD.clone = mock.Mock() - self.rbd.RBD.clone.side_effect = self.rbd.RBD.Error - self.driver._get_clone_depth = mock.Mock() - # Try with no flatten required - self.driver._get_clone_depth.return_value = 1 - - self.rbd.Image.create_snap = mock.Mock() - self.rbd.Image.protect_snap = mock.Mock() - self.rbd.Image.unprotect_snap = mock.Mock() - self.rbd.Image.remove_snap = mock.Mock() - self.rbd.Image.close = mock.Mock() - - self.assertRaises(self.rbd.RBD.Error, self.driver.create_cloned_volume, - dict(name=dst_name), dict(name=src_name)) - - self.rbd.Image.create_snap.assert_called_once() - self.rbd.Image.protect_snap.assert_called_once() - self.rbd.RBD.clone.assert_called_once() - self.rbd.Image.unprotect_snap.assert_called_once() - self.rbd.Image.remove_snap.assert_called_once() - self.rbd.Image.close.assert_called_once() - + self.mock_rbd.RBD.clone = mock.Mock() + self.mock_rbd.RBD.clone.side_effect = self.mock_rbd.RBD.Error + with mock.patch.object(self.driver, '_get_clone_depth') as \ + mock_get_clone_depth: + # Try with no flatten required + mock_get_clone_depth.return_value = 1 + + self.mock_rbd.Image.create_snap = mock.Mock() + self.mock_rbd.Image.protect_snap = mock.Mock() + self.mock_rbd.Image.unprotect_snap = mock.Mock() + self.mock_rbd.Image.remove_snap = mock.Mock() + self.mock_rbd.Image.close = mock.Mock() + + self.assertRaises(self.mock_rbd.RBD.Error, + self.driver.create_cloned_volume, + dict(name=dst_name), dict(name=src_name)) + + self.mock_rbd.Image.create_snap.assert_called_once() + self.mock_rbd.Image.protect_snap.assert_called_once() + self.mock_rbd.RBD.clone.assert_called_once() + self.mock_rbd.Image.unprotect_snap.assert_called_once() + self.mock_rbd.Image.remove_snap.assert_called_once() + self.mock_rbd.Image.close.assert_called_once() + + @common_mocks def test_good_locations(self): locations = ['rbd://fsid/pool/image/snap', 'rbd://%2F/%2F/%2F/%2F', ] map(self.driver._parse_location, locations) + @common_mocks def test_bad_locations(self): locations = ['rbd://image', 'http://path/to/somewhere/else', @@ -412,43 +469,47 @@ class RBDTestCase(test.TestCase): self.assertFalse( self.driver._is_cloneable(loc, {'disk_format': 'raw'})) - @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') - def test_cloneable(self, mock_proxy): - self.driver._get_fsid = mock.Mock() - self.driver._get_fsid.return_value = 'abc' - location = 'rbd://abc/pool/image/snap' - info = {'disk_format': 'raw'} - self.assertTrue(self.driver._is_cloneable(location, info)) - - @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') - def test_uncloneable_different_fsid(self, mock_proxy): - self.driver._get_fsid = mock.Mock() - self.driver._get_fsid.return_value = 'abc' - location = 'rbd://def/pool/image/snap' - self.assertFalse( - self.driver._is_cloneable(location, {'disk_format': 'raw'})) - - @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') - def test_uncloneable_unreadable(self, mock_proxy): - self.driver._get_fsid = mock.Mock() - self.driver._get_fsid.return_value = 'abc' - location = 'rbd://abc/pool/image/snap' - - self.rbd.Error = Exception - mock_proxy.side_effect = self.rbd.Error - - args = [location, {'disk_format': 'raw'}] - self.assertFalse(self.driver._is_cloneable(*args)) - mock_proxy.assert_called_once() + @common_mocks + def test_cloneable(self): + with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: + mock_get_fsid.return_value = 'abc' + location = 'rbd://abc/pool/image/snap' + info = {'disk_format': 'raw'} + self.assertTrue(self.driver._is_cloneable(location, info)) + self.assertTrue(mock_get_fsid.called) + + @common_mocks + def test_uncloneable_different_fsid(self): + with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: + mock_get_fsid.return_value = 'abc' + location = 'rbd://def/pool/image/snap' + self.assertFalse( + self.driver._is_cloneable(location, {'disk_format': 'raw'})) + self.assertTrue(mock_get_fsid.called) + + @common_mocks + def test_uncloneable_unreadable(self): + with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: + mock_get_fsid.return_value = 'abc' + location = 'rbd://abc/pool/image/snap' + + self.mock_proxy.side_effect = self.mock_rbd.Error + args = [location, {'disk_format': 'raw'}] + self.assertFalse(self.driver._is_cloneable(*args)) + self.mock_proxy.assert_called_once() + self.assertTrue(mock_get_fsid.called) + + @common_mocks def test_uncloneable_bad_format(self): - self.driver._get_fsid = mock.Mock() - self.driver._get_fsid.return_value = 'abc' - location = 'rbd://abc/pool/image/snap' - formats = ['qcow2', 'vmdk', 'vdi'] - for f in formats: - self.assertFalse( - self.driver._is_cloneable(location, {'disk_format': f})) + with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: + mock_get_fsid.return_value = 'abc' + location = 'rbd://abc/pool/image/snap' + formats = ['qcow2', 'vmdk', 'vdi'] + for f in formats: + self.assertFalse( + self.driver._is_cloneable(location, {'disk_format': f})) + self.assertTrue(mock_get_fsid.called) def _copy_image(self): with mock.patch.object(tempfile, 'NamedTemporaryFile'): @@ -462,17 +523,19 @@ class RBDTestCase(test.TestCase): mock_image_service, None] self.driver.copy_image_to_volume(*args) + @common_mocks def test_copy_image_no_volume_tmp(self): self.cfg.volume_tmp_dir = None self._copy_image() + @common_mocks def test_copy_image_volume_tmp(self): self.cfg.volume_tmp_dir = '/var/run/cinder/tmp' self._copy_image() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_update_volume_stats(self, mock_client): - client = mock_client.return_value + @common_mocks + def test_update_volume_stats(self): + client = self.mock_client.return_value client.__enter__.return_value = client client.cluster = mock.Mock() @@ -496,9 +559,9 @@ class RBDTestCase(test.TestCase): client.cluster.get_cluster_stats.assert_called_once() self.assertDictMatch(expected, actual) - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_update_volume_stats_error(self, mock_client): - client = mock_client.return_value + @common_mocks + def test_update_volume_stats_error(self): + client = self.mock_client.return_value client.__enter__.return_value = client client.cluster = mock.Mock() @@ -508,8 +571,6 @@ class RBDTestCase(test.TestCase): self.driver.configuration.safe_get = mock.Mock() self.driver.configuration.safe_get.return_value = 'RBD' - self.rados.Error = Exception - expected = dict(volume_backend_name='RBD', vendor_name='Open Source', driver_version=self.driver.VERSION, @@ -522,39 +583,42 @@ class RBDTestCase(test.TestCase): client.cluster.get_cluster_stats.assert_called_once() self.assertDictMatch(expected, actual) + @common_mocks def test_get_mon_addrs(self): - self.driver._execute = mock.Mock() - self.driver._execute.return_value = (CEPH_MON_DUMP, '') - - hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com'] - ports = ['6789', '6790', '6791', '6792', '6791'] - self.assertEqual((hosts, ports), self.driver._get_mon_addrs()) + with mock.patch.object(self.driver, '_execute') as mock_execute: + mock_execute.return_value = (CEPH_MON_DUMP, '') + hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com'] + ports = ['6789', '6790', '6791', '6792', '6791'] + self.assertEqual((hosts, ports), self.driver._get_mon_addrs()) + @common_mocks def test_initialize_connection(self): hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com'] ports = ['6789', '6790', '6791', '6792', '6791'] - self.driver._get_mon_addrs = mock.Mock() - self.driver._get_mon_addrs.return_value = (hosts, ports) - - expected = { - 'driver_volume_type': 'rbd', - 'data': { - 'name': '%s/%s' % (self.cfg.rbd_pool, - self.volume_name), - 'hosts': hosts, - 'ports': ports, - 'auth_enabled': False, - 'auth_username': None, - 'secret_type': 'ceph', - 'secret_uuid': None, } - } - actual = self.driver.initialize_connection(dict(name=self.volume_name), - None) - self.assertDictMatch(expected, actual) - - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_clone(self, mock_client): + with mock.patch.object(self.driver, '_get_mon_addrs') as \ + mock_get_mon_addrs: + mock_get_mon_addrs.return_value = (hosts, ports) + + expected = { + 'driver_volume_type': 'rbd', + 'data': { + 'name': '%s/%s' % (self.cfg.rbd_pool, + self.volume_name), + 'hosts': hosts, + 'ports': ports, + 'auth_enabled': False, + 'auth_username': None, + 'secret_type': 'ceph', + 'secret_uuid': None, } + } + volume = dict(name=self.volume_name) + actual = self.driver.initialize_connection(volume, None) + self.assertDictMatch(expected, actual) + self.assertTrue(mock_get_mon_addrs.called) + + @common_mocks + def test_clone(self): src_pool = u'images' src_image = u'image-name' src_snap = u'snapshot-name' @@ -567,20 +631,21 @@ class RBDTestCase(test.TestCase): return inst return _inner - client = mock_client.return_value + client = self.mock_client.return_value # capture both rados client used to perform the clone client.__enter__.side_effect = mock__enter__(client) - self.rbd.RBD.clone = mock.Mock() + self.mock_rbd.RBD.clone = mock.Mock() self.driver._clone(self.volume, src_pool, src_image, src_snap) args = [client_stack[0].ioctx, str(src_image), str(src_snap), client_stack[1].ioctx, str(self.volume_name)] - kwargs = {'features': self.rbd.RBD_FEATURE_LAYERING} - self.rbd.RBD.clone.assert_called_once_with(*args, **kwargs) + kwargs = {'features': self.mock_rbd.RBD_FEATURE_LAYERING} + self.mock_rbd.RBD.clone.assert_called_once_with(*args, **kwargs) self.assertEqual(client.__enter__.call_count, 2) + @common_mocks def test_extend_volume(self): fake_size = '20' fake_vol = {'project_id': 'testprjid', 'name': self.volume_name, @@ -596,63 +661,67 @@ class RBDTestCase(test.TestCase): self.mox.VerifyAll() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_rbd_volume_proxy_init(self, mock_client): + @common_mocks + def test_rbd_volume_proxy_init(self): snap = u'snapshot-name' - client = mock_client.return_value + client = self.mock_client.return_value client.__enter__.return_value = client - self.driver._connect_to_rados = mock.Mock() - self.driver._connect_to_rados.return_value = (None, None) - self.driver._disconnect_from_rados = mock.Mock() - self.driver._disconnect_from_rados.return_value = (None, None) + with mock.patch.object(self.driver, '_connect_to_rados') as \ + mock_connect_from_rados: + with mock.patch.object(self.driver, '_disconnect_from_rados') as \ + mock_disconnect_from_rados: + mock_connect_from_rados.return_value = (None, None) + mock_disconnect_from_rados.return_value = (None, None) - with driver.RBDVolumeProxy(self.driver, self.volume_name): - self.driver._connect_to_rados.assert_called_once() - self.assertFalse(self.driver._disconnect_from_rados.called) + with driver.RBDVolumeProxy(self.driver, self.volume_name): + mock_connect_from_rados.assert_called_once() + self.assertFalse(mock_disconnect_from_rados.called) - self.driver._disconnect_from_rados.assert_called_once() + mock_disconnect_from_rados.assert_called_once() - self.driver._connect_to_rados.reset_mock() - self.driver._disconnect_from_rados.reset_mock() + mock_connect_from_rados.reset_mock() + mock_disconnect_from_rados.reset_mock() - with driver.RBDVolumeProxy(self.driver, self.volume_name, - snapshot=snap): - self.driver._connect_to_rados.assert_called_once() - self.assertFalse(self.driver._disconnect_from_rados.called) + with driver.RBDVolumeProxy(self.driver, self.volume_name, + snapshot=snap): + mock_connect_from_rados.assert_called_once() + self.assertFalse(mock_disconnect_from_rados.called) - self.driver._disconnect_from_rados.assert_called_once() + mock_disconnect_from_rados.assert_called_once() - @mock.patch('cinder.volume.drivers.rbd.RADOSClient') - def test_connect_to_rados(self, mock_client): - client = mock_client.return_value - client.__enter__.return_value = client - client.open_ioctx = mock.Mock() - - mock_ioctx = mock.Mock() - client.open_ioctx.return_value = mock_ioctx - - self.rados.Error = test.TestingException - self.rados.Rados.return_value = client + @common_mocks + def test_connect_to_rados(self): + self.mock_rados.Rados.connect = mock.Mock() + self.mock_rados.Rados.shutdown = mock.Mock() + self.mock_rados.Rados.open_ioctx = mock.Mock() + self.mock_rados.Rados.open_ioctx.return_value = \ + self.mock_rados.Rados.ioctx # default configured pool - self.assertEqual((client, mock_ioctx), - self.driver._connect_to_rados()) - client.open_ioctx.assert_called_with(self.cfg.rbd_pool) + ret = self.driver._connect_to_rados() + self.assertTrue(self.mock_rados.Rados.connect.called) + self.assertTrue(self.mock_rados.Rados.open_ioctx.called) + self.assertIsInstance(ret[0], self.mock_rados.Rados) + self.assertEqual(ret[1], self.mock_rados.Rados.ioctx) + self.mock_rados.Rados.open_ioctx.assert_called_with(self.cfg.rbd_pool) # different pool - self.assertEqual((client, mock_ioctx), - self.driver._connect_to_rados('images')) - client.open_ioctx.assert_called_with('images') + ret = self.driver._connect_to_rados('alt_pool') + self.assertTrue(self.mock_rados.Rados.connect.called) + self.assertTrue(self.mock_rados.Rados.open_ioctx.called) + self.assertIsInstance(ret[0], self.mock_rados.Rados) + self.assertEqual(ret[1], self.mock_rados.Rados.ioctx) + self.mock_rados.Rados.open_ioctx.assert_called_with('alt_pool') # error - client.open_ioctx.reset_mock() - client.shutdown.reset_mock() - client.open_ioctx.side_effect = self.rados.Error - self.assertRaises(test.TestingException, self.driver._connect_to_rados) - client.open_ioctx.assert_called_once() - client.shutdown.assert_called_once() + self.mock_rados.Rados.open_ioctx.reset_mock() + self.mock_rados.Rados.shutdown.reset_mock() + self.mock_rados.Rados.open_ioctx.side_effect = self.mock_rados.Error + self.assertRaises(self.mock_rados.Error, self.driver._connect_to_rados) + self.mock_rados.Rados.open_ioctx.assert_called_once() + self.mock_rados.Rados.shutdown.assert_called_once() class RBDImageIOWrapperTestCase(test.TestCase): @@ -666,7 +735,7 @@ class RBDImageIOWrapperTestCase(test.TestCase): self.meta.image.read = mock.Mock() self.meta.image.write = mock.Mock() self.meta.image.size = mock.Mock() - self.rbd_wrapper = driver.RBDImageIOWrapper(self.meta) + self.mock_rbd_wrapper = driver.RBDImageIOWrapper(self.meta) self.data_length = 1024 self.full_data = 'abcd' * 256 @@ -674,25 +743,25 @@ class RBDImageIOWrapperTestCase(test.TestCase): super(RBDImageIOWrapperTestCase, self).tearDown() def test_init(self): - self.assertEqual(self.rbd_wrapper._rbd_meta, self.meta) - self.assertEqual(self.rbd_wrapper._offset, 0) + self.assertEqual(self.mock_rbd_wrapper._rbd_meta, self.meta) + self.assertEqual(self.mock_rbd_wrapper._offset, 0) def test_inc_offset(self): - self.rbd_wrapper._inc_offset(10) - self.rbd_wrapper._inc_offset(10) - self.assertEqual(self.rbd_wrapper._offset, 20) + self.mock_rbd_wrapper._inc_offset(10) + self.mock_rbd_wrapper._inc_offset(10) + self.assertEqual(self.mock_rbd_wrapper._offset, 20) def test_rbd_image(self): - self.assertEqual(self.rbd_wrapper.rbd_image, self.meta.image) + self.assertEqual(self.mock_rbd_wrapper.rbd_image, self.meta.image) def test_rbd_user(self): - self.assertEqual(self.rbd_wrapper.rbd_user, self.meta.user) + self.assertEqual(self.mock_rbd_wrapper.rbd_user, self.meta.user) def test_rbd_pool(self): - self.assertEqual(self.rbd_wrapper.rbd_conf, self.meta.conf) + self.assertEqual(self.mock_rbd_wrapper.rbd_conf, self.meta.conf) def test_rbd_conf(self): - self.assertEqual(self.rbd_wrapper.rbd_pool, self.meta.pool) + self.assertEqual(self.mock_rbd_wrapper.rbd_pool, self.meta.pool) def test_read(self): @@ -702,77 +771,77 @@ class RBDImageIOWrapperTestCase(test.TestCase): self.meta.image.read.side_effect = mock_read self.meta.image.size.return_value = self.data_length - data = self.rbd_wrapper.read() + data = self.mock_rbd_wrapper.read() self.assertEqual(data, self.full_data) - data = self.rbd_wrapper.read() + data = self.mock_rbd_wrapper.read() self.assertEqual(data, '') - self.rbd_wrapper.seek(0) - data = self.rbd_wrapper.read() + self.mock_rbd_wrapper.seek(0) + data = self.mock_rbd_wrapper.read() self.assertEqual(data, self.full_data) - self.rbd_wrapper.seek(0) - data = self.rbd_wrapper.read(10) + self.mock_rbd_wrapper.seek(0) + data = self.mock_rbd_wrapper.read(10) self.assertEqual(data, self.full_data[:10]) def test_write(self): - self.rbd_wrapper.write(self.full_data) - self.assertEqual(self.rbd_wrapper._offset, 1024) + self.mock_rbd_wrapper.write(self.full_data) + self.assertEqual(self.mock_rbd_wrapper._offset, 1024) def test_seekable(self): - self.assertTrue(self.rbd_wrapper.seekable) + self.assertTrue(self.mock_rbd_wrapper.seekable) def test_seek(self): - self.assertEqual(self.rbd_wrapper._offset, 0) - self.rbd_wrapper.seek(10) - self.assertEqual(self.rbd_wrapper._offset, 10) - self.rbd_wrapper.seek(10) - self.assertEqual(self.rbd_wrapper._offset, 10) - self.rbd_wrapper.seek(10, 1) - self.assertEqual(self.rbd_wrapper._offset, 20) - - self.rbd_wrapper.seek(0) - self.rbd_wrapper.write(self.full_data) + self.assertEqual(self.mock_rbd_wrapper._offset, 0) + self.mock_rbd_wrapper.seek(10) + self.assertEqual(self.mock_rbd_wrapper._offset, 10) + self.mock_rbd_wrapper.seek(10) + self.assertEqual(self.mock_rbd_wrapper._offset, 10) + self.mock_rbd_wrapper.seek(10, 1) + self.assertEqual(self.mock_rbd_wrapper._offset, 20) + + self.mock_rbd_wrapper.seek(0) + self.mock_rbd_wrapper.write(self.full_data) self.meta.image.size.return_value = self.data_length - self.rbd_wrapper.seek(0) - self.assertEqual(self.rbd_wrapper._offset, 0) + self.mock_rbd_wrapper.seek(0) + self.assertEqual(self.mock_rbd_wrapper._offset, 0) - self.rbd_wrapper.seek(10, 2) - self.assertEqual(self.rbd_wrapper._offset, self.data_length + 10) - self.rbd_wrapper.seek(-10, 2) - self.assertEqual(self.rbd_wrapper._offset, self.data_length - 10) + self.mock_rbd_wrapper.seek(10, 2) + self.assertEqual(self.mock_rbd_wrapper._offset, self.data_length + 10) + self.mock_rbd_wrapper.seek(-10, 2) + self.assertEqual(self.mock_rbd_wrapper._offset, self.data_length - 10) # test exceptions. - self.assertRaises(IOError, self.rbd_wrapper.seek, 0, 3) - self.assertRaises(IOError, self.rbd_wrapper.seek, -1) + self.assertRaises(IOError, self.mock_rbd_wrapper.seek, 0, 3) + self.assertRaises(IOError, self.mock_rbd_wrapper.seek, -1) # offset should not have been changed by any of the previous # operations. - self.assertEqual(self.rbd_wrapper._offset, self.data_length - 10) + self.assertEqual(self.mock_rbd_wrapper._offset, self.data_length - 10) def test_tell(self): - self.assertEqual(self.rbd_wrapper.tell(), 0) - self.rbd_wrapper._inc_offset(10) - self.assertEqual(self.rbd_wrapper.tell(), 10) + self.assertEqual(self.mock_rbd_wrapper.tell(), 0) + self.mock_rbd_wrapper._inc_offset(10) + self.assertEqual(self.mock_rbd_wrapper.tell(), 10) def test_flush(self): with mock.patch.object(driver, 'LOG') as mock_logger: self.meta.image.flush = mock.Mock() - self.rbd_wrapper.flush() + self.mock_rbd_wrapper.flush() self.meta.image.flush.assert_called_once() self.meta.image.flush.reset_mock() # this should be caught and logged silently. self.meta.image.flush.side_effect = AttributeError - self.rbd_wrapper.flush() + self.mock_rbd_wrapper.flush() self.meta.image.flush.assert_called_once() msg = _("flush() not supported in this version of librbd") mock_logger.warning.assert_called_with(msg) def test_fileno(self): - self.assertRaises(IOError, self.rbd_wrapper.fileno) + self.assertRaises(IOError, self.mock_rbd_wrapper.fileno) def test_close(self): - self.rbd_wrapper.close() + self.mock_rbd_wrapper.close() class ManagedRBDTestCase(DriverTestCase): @@ -834,55 +903,56 @@ class ManagedRBDTestCase(DriverTestCase): def test_create_vol_from_image_status_available(self): """Clone raw image then verify volume is in available state.""" - def mock_clone_image(volume, image_location, image_id, image_meta): + def _mock_clone_image(volume, image_location, image_id, image_meta): return {'provider_location': None}, True - self.volume.driver.clone_image = mock.Mock() - self.volume.driver.clone_image.side_effect = mock_clone_image - self.volume.driver.create_volume = mock.Mock() - - with mock.patch.object(create_volume.CreateVolumeFromSpecTask, - '_copy_image_to_volume') as mock_copy: - self._create_volume_from_image('available', raw=True) + with mock.patch.object(self.volume.driver, 'clone_image') as \ + mock_clone_image: + mock_clone_image.side_effect = _mock_clone_image + with mock.patch.object(self.volume.driver, 'create_volume') as \ + mock_create: + with mock.patch.object(create_volume.CreateVolumeFromSpecTask, + '_copy_image_to_volume') as mock_copy: + self._create_volume_from_image('available', raw=True) + self.assertFalse(mock_copy.called) - self.volume.driver.clone_image.assert_called_once() - self.assertFalse(self.volume.driver.create_volume.called) - self.assertFalse(mock_copy.called) + mock_clone_image.assert_called_once() + self.assertFalse(mock_create.called) def test_create_vol_from_non_raw_image_status_available(self): """Clone non-raw image then verify volume is in available state.""" - def mock_clone_image(volume, image_location, image_id, image_meta): + def _mock_clone_image(volume, image_location, image_id, image_meta): return {'provider_location': None}, False - self.volume.driver.clone_image = mock.Mock() - self.volume.driver.clone_image.side_effect = mock_clone_image - self.volume.driver.create_volume = mock.Mock() - self.volume.driver.create_volume.return_value = None + with mock.patch.object(self.volume.driver, 'clone_image') as \ + mock_clone_image: + mock_clone_image.side_effect = _mock_clone_image + with mock.patch.object(self.volume.driver, 'create_volume') as \ + mock_create: + with mock.patch.object(create_volume.CreateVolumeFromSpecTask, + '_copy_image_to_volume') as mock_copy: + self._create_volume_from_image('available', raw=False) + mock_copy.assert_called_once() - with mock.patch.object(create_volume.CreateVolumeFromSpecTask, - '_copy_image_to_volume') as mock_copy: - self._create_volume_from_image('available', raw=False) - - self.volume.driver.clone_image.assert_called_once() - self.volume.driver.create_volume.assert_called_once() - mock_copy.assert_called_once() + mock_clone_image.assert_called_once() + mock_create.assert_called_once() def test_create_vol_from_image_status_error(self): """Fail to clone raw image then verify volume is in error state.""" - - self.volume.driver.clone_image = mock.Mock() - self.volume.driver.clone_image.side_effect = exception.CinderException - self.volume.driver.create_volume = mock.Mock() - self.volume.driver.create_volume.return_value = None - - with mock.patch.object(create_volume.CreateVolumeFromSpecTask, - '_copy_image_to_volume') as mock_copy: - self._create_volume_from_image('error', raw=True, clone_error=True) - - self.volume.driver.clone_image.assert_called_once() - self.assertFalse(self.volume.driver.create_volume.called) - self.assertFalse(mock_copy.called) + with mock.patch.object(self.volume.driver, 'clone_image') as \ + mock_clone_image: + mock_clone_image.side_effect = exception.CinderException + with mock.patch.object(self.volume.driver, 'create_volume') as \ + mock_create: + with mock.patch.object(create_volume.CreateVolumeFromSpecTask, + '_copy_image_to_volume') as mock_copy: + self._create_volume_from_image('error', raw=True, + clone_error=True) + self.assertFalse(mock_copy.called) + + mock_clone_image.assert_called_once() + self.assertFalse(self.volume.driver.create_volume.called) def test_clone_failure(self): driver = self.volume.driver @@ -900,17 +970,19 @@ class ManagedRBDTestCase(DriverTestCase): expected = ({'provider_location': None}, True) driver = self.volume.driver - self.volume.driver._is_cloneable = mock.Mock() - self.volume.driver._is_cloneable.return_value = True - self.volume.driver._clone = mock.Mock() - self.volume.driver._resize = mock.Mock() - - image_loc = ('rbd://fee/fi/fo/fum', None) - actual = driver.clone_image({'name': 'vol1'}, - image_loc, - 'id.foo', - {'disk_format': 'raw'}) - - self.assertEqual(expected, actual) - self.volume.driver._clone.assert_called_once() - self.volume.driver._resize.assert_called_once() + with mock.patch.object(self.volume.driver, '_is_cloneable') as \ + mock_is_cloneable: + mock_is_cloneable.return_value = True + with mock.patch.object(self.volume.driver, '_clone') as \ + mock_clone: + with mock.patch.object(self.volume.driver, '_resize') as \ + mock_resize: + image_loc = ('rbd://fee/fi/fo/fum', None) + + actual = driver.clone_image({'name': 'vol1'}, image_loc, + 'id.foo', + {'disk_format': 'raw'}) + + self.assertEqual(expected, actual) + mock_clone.assert_called_once() + mock_resize.assert_called_once() -- 2.45.2