Пример #1
0
    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
            })
Пример #2
0
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')