manila/manila/tests/data/test_manager.py
Rodrigo Barbieri 9639e72692 Share migration Newton improvements
At Austin 2016 summit there were several improvements to
Share migration feature discussed. This patch implements
these changes.

Changes are:
- Added 'Writable' API parameter: user chooses whether share must
remain writable during migration.
- Added 'Preserve Metadata' API parameter: user chooses whether
share must preserve all file metadata on migration.
- Added 'Non-disruptive' API parameter: user chooses whether
migration of share must be performed non-disruptively.
- Removed existing 'Notify', thus removing 1-phase migration
possibility.
- Renamed existing 'Force Host Copy' parameter to 'Force
Host-assisted Migration'.
- Renamed all 'migration_info' and 'migration_get_info' entries to
'connection_info' and 'connection_get_info'.
- Updated driver interfaces with the new API parameters, drivers
must respect them.
- Changed share/api => scheduler RPCAPI back to asynchronous.
- Added optional SHA-256 validation to perform additional check if
bytes were corrupted during copying.
- Added mount options configuration to Data Service so CIFS shares
can be mounted.
- Driver may override _get_access_mapping if supports a different
access_type/protocol combination than what is defined by default.
- Added CIFS share protocol support and 'user' access type
support to Data Service.
- Reset Task State API now allows task_state to be unset using
'None' value.
- Added possibility to change share-network when migrating a share.
- Bumped microversion to 2.22.
- Removed support of all previous versions of Share Migration APIs.

APIImpact
DocImpact

Implements: blueprint newton-migration-improvements
Change-Id: Ief49a46c86ed3c22d3b31021aff86a9ce0ecbe3b
2016-08-31 12:38:14 -03:00

443 lines
17 KiB
Python

# Copyright 2015, Hitachi Data Systems.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests For Data Manager
"""
import ddt
import mock
from manila.common import constants
from manila import context
from manila.data import helper
from manila.data import manager
from manila.data import utils as data_utils
from manila import db
from manila import exception
from manila.share import rpcapi as share_rpc
from manila import test
from manila.tests import db_utils
from manila import utils
@ddt.ddt
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('mount_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(None, Exception('fake'), exception.ShareDataCopyCancelled(
src_instance='ins1',
dest_instance='ins2'))
def test_migration_start(self, exc):
# mocks
self.mock_object(db, 'share_get', mock.Mock(return_value=self.share))
self.mock_object(db, 'share_instance_get', mock.Mock(
return_value=self.share.instance))
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')
else:
self.assertRaises(
exception.ShareDataCopyFailed, self.manager.migration_start,
self.context, [], self.share['id'], 'ins1_id', 'ins2_id',
'info_src', 'info_dest')
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 exc:
share_rpc.ShareAPI.migration_complete.assert_called_once_with(
self.context, self.share.instance, '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'])
connection_info_src = {'mount': 'mount_cmd_src',
'unmount': 'unmount_cmd_src'}
connection_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(db, 'share_instance_get',
mock.Mock(side_effect=[self.share['instance'],
self.share['instance']]))
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', connection_info_src,
connection_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', connection_info_src, connection_info_dest)
else:
self.manager._copy_share_data(
self.context, fake_copy, self.share, 'ins1_id',
'ins2_id', connection_info_src, connection_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['instance'], connection_info_src,
self.share['instance'], connection_info_dest))
helper.DataServiceHelper.mount_share_instance.assert_has_calls([
mock.call(connection_info_src['mount'], '/tmp/',
self.share['instance']),
mock.call(connection_info_dest['mount'], '/tmp/',
self.share['instance'])])
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(connection_info_src['unmount'], '/tmp/', 'ins1_id'),
mock.call(connection_info_dest['unmount'], '/tmp/', 'ins2_id')])
helper.DataServiceHelper.deny_access_to_data_service.assert_has_calls([
mock.call([access], self.share['instance']),
mock.call([access], self.share['instance'])])
def test__copy_share_data_exception_access(self):
connection_info_src = {'mount': 'mount_cmd_src',
'unmount': 'unmount_cmd_src'}
connection_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(db, 'share_instance_get',
mock.Mock(side_effect=[self.share['instance'],
self.share['instance']]))
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',
connection_info_src, connection_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['instance'], connection_info_src,
self.share['instance'], connection_info_dest))
def test__copy_share_data_exception_mount_1(self):
access = db_utils.create_access(share_id=self.share['id'])
connection_info_src = {'mount': 'mount_cmd_src',
'unmount': 'unmount_cmd_src'}
connection_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(db, 'share_instance_get',
mock.Mock(side_effect=[self.share['instance'],
self.share['instance']]))
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',
connection_info_src, connection_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['instance'], connection_info_src,
self.share['instance'], connection_info_dest))
helper.DataServiceHelper.mount_share_instance.assert_called_once_with(
connection_info_src['mount'], '/tmp/', self.share['instance'])
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'])
connection_info_src = {'mount': 'mount_cmd_src',
'unmount': 'unmount_cmd_src'}
connection_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(db, 'share_instance_get',
mock.Mock(side_effect=[self.share['instance'],
self.share['instance']]))
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',
connection_info_src, connection_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['instance'], connection_info_src,
self.share['instance'], connection_info_dest))
helper.DataServiceHelper.mount_share_instance.assert_has_calls([
mock.call(connection_info_src['mount'], '/tmp/',
self.share['instance']),
mock.call(connection_info_dest['mount'], '/tmp/',
self.share['instance'])])
(helper.DataServiceHelper.cleanup_unmount_temp_folder.
assert_called_once_with(
connection_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')