def _copy_share_data(self, context, copy, src_share, share_instance_id, dest_share_instance_id, connection_info_src, connection_info_dest): copied = False mount_path = CONF.mount_tmp_location share_instance = self.db.share_instance_get(context, share_instance_id, with_share_data=True) dest_share_instance = self.db.share_instance_get( context, dest_share_instance_id, with_share_data=True) self.db.share_update( context, src_share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}) helper_src = helper.DataServiceHelper(context, self.db, src_share) helper_dest = helper_src access_ref_list_src = helper_src.allow_access_to_data_service( share_instance, connection_info_src, dest_share_instance, connection_info_dest) access_ref_list_dest = access_ref_list_src def _call_cleanups(items): for item in items: if 'unmount_src' == item: helper_src.cleanup_unmount_temp_folder( connection_info_src['unmount'], mount_path, share_instance_id) elif 'temp_folder_src' == item: helper_src.cleanup_temp_folder(share_instance_id, mount_path) elif 'temp_folder_dest' == item: helper_dest.cleanup_temp_folder(dest_share_instance_id, mount_path) elif 'access_src' == item: helper_src.cleanup_data_access(access_ref_list_src, share_instance_id) elif 'access_dest' == item: helper_dest.cleanup_data_access(access_ref_list_dest, dest_share_instance_id) try: helper_src.mount_share_instance(connection_info_src['mount'], mount_path, share_instance) except Exception: msg = _("Data copy failed attempting to mount " "share instance %s.") % share_instance_id LOG.exception(msg) _call_cleanups(['temp_folder_src', 'access_dest', 'access_src']) raise exception.ShareDataCopyFailed(reason=msg) try: helper_dest.mount_share_instance(connection_info_dest['mount'], mount_path, dest_share_instance) except Exception: msg = _("Data copy failed attempting to mount " "share instance %s.") % dest_share_instance_id LOG.exception(msg) _call_cleanups([ 'temp_folder_dest', 'unmount_src', 'temp_folder_src', 'access_dest', 'access_src' ]) raise exception.ShareDataCopyFailed(reason=msg) self.busy_tasks_shares[src_share['id']] = copy self.db.share_update( context, src_share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_IN_PROGRESS}) try: copy.run() self.db.share_update( context, src_share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_COMPLETING}) if copy.get_progress()['total_progress'] == 100: copied = True except Exception: LOG.exception( "Failed to copy data from share instance " "%(share_instance_id)s to " "%(dest_share_instance_id)s.", { 'share_instance_id': share_instance_id, 'dest_share_instance_id': dest_share_instance_id }) try: helper_src.unmount_share_instance(connection_info_src['unmount'], mount_path, share_instance_id) except Exception: LOG.exception( "Could not unmount folder of instance" " %s after its data copy.", share_instance_id) try: helper_dest.unmount_share_instance(connection_info_dest['unmount'], mount_path, dest_share_instance_id) except Exception: LOG.exception( "Could not unmount folder of instance" " %s after its data copy.", dest_share_instance_id) try: helper_src.deny_access_to_data_service(access_ref_list_src, share_instance) except Exception: LOG.exception( "Could not deny access to instance" " %s after its data copy.", share_instance_id) try: helper_dest.deny_access_to_data_service(access_ref_list_dest, dest_share_instance) except Exception: LOG.exception( "Could not deny access to instance" " %s after its data copy.", dest_share_instance_id) if copy and copy.cancelled: self.db.share_update( context, src_share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_CANCELLED}) LOG.warning( "Copy of data from share instance " "%(src_instance)s to share instance " "%(dest_instance)s was cancelled.", { 'src_instance': share_instance_id, 'dest_instance': dest_share_instance_id }) raise exception.ShareDataCopyCancelled( src_instance=share_instance_id, dest_instance=dest_share_instance_id) elif not copied: msg = _("Copying data from share instance %(instance_id)s " "to %(dest_instance_id)s did not succeed.") % ( { 'instance_id': share_instance_id, 'dest_instance_id': dest_share_instance_id }) raise exception.ShareDataCopyFailed(reason=msg) self.db.share_update( context, src_share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_COMPLETED}) LOG.debug( "Copy of data from share instance %(src_instance)s to " "share instance %(dest_instance)s was successful.", { 'src_instance': share_instance_id, 'dest_instance': dest_share_instance_id })
class DataManagerTestCase(test.TestCase): """Test case for data manager.""" def setUp(self): super(DataManagerTestCase, self).setUp() self.manager = manager.DataManager() self.context = context.get_admin_context() self.topic = 'fake_topic' self.share = db_utils.create_share() manager.CONF.set_default('migration_tmp_location', '/tmp/') def test_init(self): manager = self.manager self.assertIsNotNone(manager) @ddt.data(constants.TASK_STATE_DATA_COPYING_COMPLETING, constants.TASK_STATE_DATA_COPYING_STARTING, constants.TASK_STATE_DATA_COPYING_IN_PROGRESS) def test_init_host(self, status): share = db_utils.create_share(task_state=status) # mocks self.mock_object(db, 'share_get_all', mock.Mock(return_value=[share])) self.mock_object(db, 'share_update') # run self.manager.init_host() # asserts db.share_get_all.assert_called_once_with( utils.IsAMatcher(context.RequestContext)) db.share_update.assert_called_with( utils.IsAMatcher(context.RequestContext), share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_ERROR}) @ddt.data({ 'notify': True, 'exc': None }, { 'notify': False, 'exc': None }, { 'notify': 'fake', 'exc': exception.ShareDataCopyCancelled(src_instance='ins1', dest_instance='ins2') }, { 'notify': 'fake', 'exc': Exception('fake') }) @ddt.unpack def test_migration_start(self, notify, exc): # mocks self.mock_object(db, 'share_get', mock.Mock(return_value=self.share)) self.mock_object(data_utils, 'Copy', mock.Mock(return_value='fake_copy')) if exc is None: self.manager.busy_tasks_shares[self.share['id']] = 'fake_copy' self.mock_object(self.manager, '_copy_share_data', mock.Mock(side_effect=exc)) self.mock_object(share_rpc.ShareAPI, 'migration_complete') if exc is not None and not isinstance( exc, exception.ShareDataCopyCancelled): self.mock_object(db, 'share_update') # run if exc is None or isinstance(exc, exception.ShareDataCopyCancelled): self.manager.migration_start(self.context, [], self.share['id'], 'ins1_id', 'ins2_id', 'info_src', 'info_dest', notify) else: self.assertRaises(exception.ShareDataCopyFailed, self.manager.migration_start, self.context, [], self.share['id'], 'ins1_id', 'ins2_id', 'info_src', 'info_dest', notify) db.share_update.assert_called_once_with( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_ERROR}) # asserts self.assertFalse(self.manager.busy_tasks_shares.get(self.share['id'])) self.manager._copy_share_data.assert_called_once_with( self.context, 'fake_copy', self.share, 'ins1_id', 'ins2_id', 'info_src', 'info_dest') if notify or exc: share_rpc.ShareAPI.migration_complete.assert_called_once_with( self.context, self.share, 'ins1_id', 'ins2_id') @ddt.data({ 'cancelled': False, 'exc': None }, { 'cancelled': False, 'exc': Exception('fake') }, { 'cancelled': True, 'exc': None }) @ddt.unpack def test__copy_share_data(self, cancelled, exc): access = db_utils.create_access(share_id=self.share['id']) migration_info_src = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } migration_info_dest = { 'mount': 'mount_cmd_dest', 'unmount': 'unmount_cmd_dest' } get_progress = {'total_progress': 100} # mocks fake_copy = mock.MagicMock(cancelled=cancelled) self.mock_object(db, 'share_update') self.mock_object(helper.DataServiceHelper, 'allow_access_to_data_service', mock.Mock(return_value=access)) self.mock_object(helper.DataServiceHelper, 'mount_share_instance') self.mock_object(fake_copy, 'run', mock.Mock(side_effect=exc)) self.mock_object(fake_copy, 'get_progress', mock.Mock(return_value=get_progress)) self.mock_object(helper.DataServiceHelper, 'unmount_share_instance', mock.Mock(side_effect=Exception('fake'))) self.mock_object(helper.DataServiceHelper, 'deny_access_to_data_service', mock.Mock(side_effect=Exception('fake'))) extra_updates = None # run if cancelled: self.assertRaises(exception.ShareDataCopyCancelled, self.manager._copy_share_data, self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) extra_updates = [ mock.call(self.context, self.share['id'], { 'task_state': constants.TASK_STATE_DATA_COPYING_COMPLETING }), mock.call(self.context, self.share['id'], { 'task_state': constants.TASK_STATE_DATA_COPYING_CANCELLED }) ] elif exc: self.assertRaises(exception.ShareDataCopyFailed, self.manager._copy_share_data, self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) else: self.manager._copy_share_data(self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) extra_updates = [ mock.call(self.context, self.share['id'], { 'task_state': constants.TASK_STATE_DATA_COPYING_COMPLETING }), mock.call(self.context, self.share['id'], { 'task_state': constants.TASK_STATE_DATA_COPYING_COMPLETED }) ] # asserts self.assertEqual(self.manager.busy_tasks_shares[self.share['id']], fake_copy) update_list = [ mock.call( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}), mock.call( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_IN_PROGRESS}), ] if extra_updates: update_list = update_list + extra_updates db.share_update.assert_has_calls(update_list) helper.DataServiceHelper.allow_access_to_data_service.\ assert_called_once_with(self.share, 'ins1_id', 'ins2_id') helper.DataServiceHelper.mount_share_instance.assert_has_calls([ mock.call(migration_info_src['mount'], '/tmp/', 'ins1_id'), mock.call(migration_info_dest['mount'], '/tmp/', 'ins2_id') ]) fake_copy.run.assert_called_once_with() if exc is None: fake_copy.get_progress.assert_called_once_with() helper.DataServiceHelper.unmount_share_instance.assert_has_calls([ mock.call(migration_info_src['unmount'], '/tmp/', 'ins1_id'), mock.call(migration_info_dest['unmount'], '/tmp/', 'ins2_id') ]) helper.DataServiceHelper.deny_access_to_data_service.assert_has_calls( [mock.call(access, 'ins1_id'), mock.call(access, 'ins2_id')]) def test__copy_share_data_exception_access(self): migration_info_src = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } migration_info_dest = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } fake_copy = mock.MagicMock(cancelled=False) # mocks self.mock_object(db, 'share_update') self.mock_object( helper.DataServiceHelper, 'allow_access_to_data_service', mock.Mock(side_effect=exception.ShareDataCopyFailed( reason='fake'))) self.mock_object(helper.DataServiceHelper, 'cleanup_data_access') # run self.assertRaises(exception.ShareDataCopyFailed, self.manager._copy_share_data, self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) # asserts db.share_update.assert_called_once_with( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}) helper.DataServiceHelper.allow_access_to_data_service.\ assert_called_once_with(self.share, 'ins1_id', 'ins2_id') def test__copy_share_data_exception_mount_1(self): access = db_utils.create_access(share_id=self.share['id']) migration_info_src = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } migration_info_dest = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } fake_copy = mock.MagicMock(cancelled=False) # mocks self.mock_object(db, 'share_update') self.mock_object(helper.DataServiceHelper, 'allow_access_to_data_service', mock.Mock(return_value=access)) self.mock_object(helper.DataServiceHelper, 'mount_share_instance', mock.Mock(side_effect=Exception('fake'))) self.mock_object(helper.DataServiceHelper, 'cleanup_data_access') self.mock_object(helper.DataServiceHelper, 'cleanup_temp_folder') # run self.assertRaises(exception.ShareDataCopyFailed, self.manager._copy_share_data, self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) # asserts db.share_update.assert_called_once_with( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}) helper.DataServiceHelper.allow_access_to_data_service.\ assert_called_once_with(self.share, 'ins1_id', 'ins2_id') helper.DataServiceHelper.mount_share_instance.assert_called_once_with( migration_info_src['mount'], '/tmp/', 'ins1_id') helper.DataServiceHelper.cleanup_temp_folder.assert_called_once_with( 'ins1_id', '/tmp/') helper.DataServiceHelper.cleanup_data_access.assert_has_calls( [mock.call(access, 'ins2_id'), mock.call(access, 'ins1_id')]) def test__copy_share_data_exception_mount_2(self): access = db_utils.create_access(share_id=self.share['id']) migration_info_src = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } migration_info_dest = { 'mount': 'mount_cmd_src', 'unmount': 'unmount_cmd_src' } fake_copy = mock.MagicMock(cancelled=False) # mocks self.mock_object(db, 'share_update') self.mock_object(helper.DataServiceHelper, 'allow_access_to_data_service', mock.Mock(return_value=access)) self.mock_object(helper.DataServiceHelper, 'mount_share_instance', mock.Mock(side_effect=[None, Exception('fake')])) self.mock_object(helper.DataServiceHelper, 'cleanup_data_access') self.mock_object(helper.DataServiceHelper, 'cleanup_temp_folder') self.mock_object(helper.DataServiceHelper, 'cleanup_unmount_temp_folder') # run self.assertRaises(exception.ShareDataCopyFailed, self.manager._copy_share_data, self.context, fake_copy, self.share, 'ins1_id', 'ins2_id', migration_info_src, migration_info_dest) # asserts db.share_update.assert_called_once_with( self.context, self.share['id'], {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}) helper.DataServiceHelper.allow_access_to_data_service.\ assert_called_once_with(self.share, 'ins1_id', 'ins2_id') helper.DataServiceHelper.mount_share_instance.assert_has_calls([ mock.call(migration_info_src['mount'], '/tmp/', 'ins1_id'), mock.call(migration_info_dest['mount'], '/tmp/', 'ins2_id') ]) helper.DataServiceHelper.cleanup_unmount_temp_folder.\ assert_called_once_with( migration_info_src['unmount'], '/tmp/', 'ins1_id') helper.DataServiceHelper.cleanup_temp_folder.assert_has_calls( [mock.call('ins2_id', '/tmp/'), mock.call('ins1_id', '/tmp/')]) helper.DataServiceHelper.cleanup_data_access.assert_has_calls( [mock.call(access, 'ins2_id'), mock.call(access, 'ins1_id')]) def test_data_copy_cancel(self): share = db_utils.create_share() self.manager.busy_tasks_shares[share['id']] = data_utils.Copy # mocks self.mock_object(data_utils.Copy, 'cancel') # run self.manager.data_copy_cancel(self.context, share['id']) # asserts data_utils.Copy.cancel.assert_called_once_with() def test_data_copy_cancel_not_copying(self): self.assertRaises(exception.InvalidShare, self.manager.data_copy_cancel, self.context, 'fake_id') def test_data_copy_get_progress(self): share = db_utils.create_share() self.manager.busy_tasks_shares[share['id']] = data_utils.Copy expected = 'fake_progress' # mocks self.mock_object(data_utils.Copy, 'get_progress', mock.Mock(return_value=expected)) # run result = self.manager.data_copy_get_progress(self.context, share['id']) # asserts self.assertEqual(expected, result) data_utils.Copy.get_progress.assert_called_once_with() def test_data_copy_get_progress_not_copying(self): self.assertRaises(exception.InvalidShare, self.manager.data_copy_get_progress, self.context, 'fake_id')