Update Share Migration CLI
Implemented new parameters: - 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 'Notify' parameter. - Renamed existing 'Force Host Copy' parameter to 'Skip Optimized Migration'. - Added 'New Share Network' API parameter: user chooses new share network to migrate to if desired. Implements: blueprint newton-migration-improvements Depends-On: Ief49a46c86ed3c22d3b31021aff86a9ce0ecbe3b Change-Id: I6e7a1d854d9a09f10511c5d47715b1979813f947
This commit is contained in:
parent
283c96e9c5
commit
800b8003b1
@ -27,7 +27,7 @@ from manilaclient import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
MAX_VERSION = '2.21'
|
||||
MAX_VERSION = '2.22'
|
||||
MIN_VERSION = '2.0'
|
||||
DEPRECATED_VERSION = '1.0'
|
||||
_VERSIONED_METHOD_MAP = {}
|
||||
|
@ -896,6 +896,13 @@ class ManilaCLIClient(base.CLIClient):
|
||||
"within the required time (%s s)." % self.build_timeout)
|
||||
raise tempest_lib_exc.TimeoutException(message)
|
||||
|
||||
def reset_task_state(self, share_id, state, version=None):
|
||||
state = '--task_state %s' % state if state else ''
|
||||
return self.manila('reset-task-state %(state)s %(share)s' % {
|
||||
'state': state,
|
||||
'share': share_id,
|
||||
}, microversion=version)
|
||||
|
||||
def create_security_service(self, type='ldap', name=None, description=None,
|
||||
dns_ip=None, server=None, domain=None,
|
||||
user=None, password=None, microversion=None):
|
||||
|
@ -13,10 +13,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
from tempest.lib.common.utils import data_utils
|
||||
|
||||
from manilaclient import config
|
||||
from manilaclient.tests.functional import base
|
||||
from manilaclient.tests.functional import utils
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
@ -89,6 +92,28 @@ class SharesReadWriteBase(base.BaseTestCase):
|
||||
self.assertTrue(get.get('export_locations', []) > 0)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class SharesTestMigration(base.BaseTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(SharesTestMigration, cls).setUpClass()
|
||||
|
||||
cls.share = cls.create_share(
|
||||
share_protocol='nfs',
|
||||
size=1,
|
||||
name=data_utils.rand_name('autotest_share_name'),
|
||||
client=cls.get_user_client(),
|
||||
cleanup_in_class=True)
|
||||
|
||||
@utils.skip_if_microversion_not_supported('2.22')
|
||||
@ddt.data('migration_error', 'migration_success', 'None')
|
||||
def test_reset_task_state(self, state):
|
||||
self.admin_client.reset_task_state(self.share['id'], state)
|
||||
share = self.admin_client.get_share(self.share['id'])
|
||||
self.assertEqual(state, share['task_state'])
|
||||
|
||||
|
||||
class NFSSharesReadWriteTest(SharesReadWriteBase):
|
||||
protocol = 'nfs'
|
||||
|
||||
|
@ -51,10 +51,13 @@ class FakeClient(object):
|
||||
if body is not None:
|
||||
actual = self.client.callstack[pos][2]
|
||||
|
||||
assert actual == body, "Expected %(b)s; got %(a)s" % {
|
||||
'b': body,
|
||||
'a': actual
|
||||
}
|
||||
if isinstance(actual, dict) and isinstance(body, dict):
|
||||
assert sorted(list(actual)) == sorted(list(body))
|
||||
else:
|
||||
assert actual == body, "Expected %(b)s; got %(a)s" % {
|
||||
'b': body,
|
||||
'a': actual
|
||||
}
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test."""
|
||||
|
@ -367,6 +367,19 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
||||
assert body[action]['new_size'] is not None
|
||||
elif action in ('unmanage', ):
|
||||
assert body[action] is None
|
||||
elif action in (
|
||||
'migration_cancel', 'migration_complete',
|
||||
'migration_get_progress'):
|
||||
assert body[action] is None
|
||||
if 'migration_get_progress' == action:
|
||||
_body = {'total_progress': 50}
|
||||
return 200, {}, _body
|
||||
elif action in (
|
||||
'os-migrate_share', 'migrate_share',
|
||||
'migration_start'):
|
||||
assert 'host' in body[action]
|
||||
elif action == 'reset_task_state':
|
||||
assert 'task_state' in body[action]
|
||||
else:
|
||||
raise AssertionError("Unexpected share action: %s" % action)
|
||||
return (resp, {}, _body)
|
||||
|
@ -521,33 +521,26 @@ class SharesTest(utils.TestCase):
|
||||
cs.shares.list_instances(share)
|
||||
cs.assert_called('GET', '/shares/1234/instances')
|
||||
|
||||
@ddt.data(
|
||||
("2.6", "os-migrate_share"),
|
||||
("2.7", "migrate_share"),
|
||||
("2.14", "migrate_share"),
|
||||
("2.15", "migration_start"),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_migration_start(self, microversion, action_name):
|
||||
def test_migration_start(self):
|
||||
share = "fake_share"
|
||||
host = "fake_host"
|
||||
force_host_copy = "fake_force_host_copy"
|
||||
version = api_versions.APIVersion(microversion)
|
||||
version = api_versions.APIVersion('2.22')
|
||||
manager = shares.ShareManager(
|
||||
api=fakes.FakeClient(api_version=version))
|
||||
|
||||
with mock.patch.object(manager, "_action",
|
||||
mock.Mock(return_value="fake")):
|
||||
if version < api_versions.APIVersion('2.15'):
|
||||
result = manager.migration_start(share, host, force_host_copy)
|
||||
else:
|
||||
result = manager.migration_start(share, host, force_host_copy,
|
||||
True)
|
||||
|
||||
result = manager.migration_start(share, host, True)
|
||||
manager._action.assert_called_once_with(
|
||||
action_name, share,
|
||||
{"host": host, "force_host_copy": force_host_copy,
|
||||
"notify": True})
|
||||
'migration_start', share, {
|
||||
"host": host,
|
||||
"force_host_assisted_migration": True,
|
||||
"preserve_metadata": True,
|
||||
"writable": True,
|
||||
"nondisruptive": False,
|
||||
"new_share_network_id": None,
|
||||
})
|
||||
|
||||
self.assertEqual("fake", result)
|
||||
|
||||
def test_migration_complete(self):
|
||||
|
@ -1871,3 +1871,35 @@ class ShellTest(test_utils.TestCase):
|
||||
expected = {'reset_status': {'status': 'available'}}
|
||||
self.assert_called('POST', '/snapshot-instances/1234/action',
|
||||
body=expected)
|
||||
|
||||
def test_migration_start(self):
|
||||
command = ("migration-start --preserve-metadata False --writable False"
|
||||
" --force-host-assisted-migration True "
|
||||
"--non-disruptive True --new-share-network 1111 "
|
||||
"1234 host@backend#pool")
|
||||
self.run_command(command)
|
||||
expected = {'migration_start': {
|
||||
'host': 'host@backend#pool',
|
||||
'force_host_assisted_migration': 'True',
|
||||
'preserve-metadata': 'False',
|
||||
'writable': 'False',
|
||||
'nondisruptive': 'True',
|
||||
'new_share_network_id': '1111',
|
||||
}}
|
||||
self.assert_called('POST', '/shares/1234/action', body=expected)
|
||||
|
||||
@ddt.data('migration-complete', 'migration-get-progress',
|
||||
'migration-cancel')
|
||||
def test_migration_others(self, method):
|
||||
command = ' '.join((method, '1234'))
|
||||
self.run_command(command)
|
||||
expected = {method.replace('-', '_'): None}
|
||||
self.assert_called('POST', '/shares/1234/action', body=expected)
|
||||
|
||||
@ddt.data('migration_error', 'migration_success', None)
|
||||
def test_reset_task_state(self, param):
|
||||
command = ' '.join(('reset-task-state --state', six.text_type(param),
|
||||
'1234'))
|
||||
self.run_command(command)
|
||||
expected = {'reset_task_state': {'task_state': param}}
|
||||
self.assert_called('POST', '/shares/1234/action', body=expected)
|
||||
|
@ -43,9 +43,13 @@ class Share(common_base.Resource):
|
||||
"""Unmanage this share."""
|
||||
self.manager.unmanage(self, **kwargs)
|
||||
|
||||
def migration_start(self, host, force_host_copy, notify=True):
|
||||
def migration_start(self, host, force_host_assisted_migration,
|
||||
preserve_metadata=True, writable=True,
|
||||
nondisruptive=False, new_share_network_id=None):
|
||||
"""Migrate the share to a new host."""
|
||||
self.manager.migration_start(self, host, force_host_copy, notify)
|
||||
self.manager.migration_start(self, host, force_host_assisted_migration,
|
||||
preserve_metadata, writable,
|
||||
nondisruptive, new_share_network_id)
|
||||
|
||||
def migration_complete(self):
|
||||
"""Complete migration of a share."""
|
||||
@ -144,42 +148,22 @@ class ShareManager(base.ManagerWithFind):
|
||||
}
|
||||
return self._create('/shares', {'share': body}, 'share')
|
||||
|
||||
def _do_migrate_start(self, share, host, force_host_copy, notify,
|
||||
action_name):
|
||||
"""Migrate share to new host and pool.
|
||||
|
||||
:param share: The :class:'share' to migrate
|
||||
:param host: The destination host and pool
|
||||
:param force_host_copy: Skip driver optimizations
|
||||
:param notify: whether migration completion should be notified
|
||||
:param action_name: action name to be used in request. Changes
|
||||
according to desired microversion.
|
||||
"""
|
||||
|
||||
@api_versions.wraps("2.22")
|
||||
@api_versions.experimental_api
|
||||
def migration_start(self, share, host, force_host_assisted_migration,
|
||||
preserve_metadata=True, writable=True,
|
||||
nondisruptive=False, new_share_network_id=None):
|
||||
return self._action(
|
||||
action_name, share,
|
||||
{"host": host, "force_host_copy": force_host_copy,
|
||||
"notify": notify})
|
||||
"migration_start", share, {
|
||||
"host": host,
|
||||
"force_host_assisted_migration": force_host_assisted_migration,
|
||||
"preserve_metadata": preserve_metadata,
|
||||
"writable": writable,
|
||||
"nondisruptive": nondisruptive,
|
||||
"new_share_network_id": new_share_network_id,
|
||||
})
|
||||
|
||||
@api_versions.wraps("2.5", "2.6")
|
||||
@api_versions.experimental_api
|
||||
def migration_start(self, share, host, force_host_copy):
|
||||
return self._do_migrate_start(
|
||||
share, host, force_host_copy, True, "os-migrate_share")
|
||||
|
||||
@api_versions.wraps("2.7", "2.14") # noqa
|
||||
@api_versions.experimental_api
|
||||
def migration_start(self, share, host, force_host_copy):
|
||||
return self._do_migrate_start(
|
||||
share, host, force_host_copy, True, "migrate_share")
|
||||
|
||||
@api_versions.wraps("2.15") # noqa
|
||||
@api_versions.experimental_api
|
||||
def migration_start(self, share, host, force_host_copy, notify):
|
||||
return self._do_migrate_start(
|
||||
share, host, force_host_copy, notify, "migration_start")
|
||||
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
@api_versions.experimental_api
|
||||
def reset_task_state(self, share, task_state):
|
||||
"""Update the provided share with the provided task state.
|
||||
@ -190,7 +174,7 @@ class ShareManager(base.ManagerWithFind):
|
||||
return self._action('reset_task_state', share,
|
||||
{"task_state": task_state})
|
||||
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
@api_versions.experimental_api
|
||||
def migration_complete(self, share):
|
||||
"""Completes migration for a given share.
|
||||
@ -199,7 +183,7 @@ class ShareManager(base.ManagerWithFind):
|
||||
"""
|
||||
return self._action('migration_complete', share)
|
||||
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
@api_versions.experimental_api
|
||||
def migration_cancel(self, share):
|
||||
"""Attempts to cancel migration for a given share.
|
||||
@ -208,7 +192,7 @@ class ShareManager(base.ManagerWithFind):
|
||||
"""
|
||||
return self._action('migration_cancel', share)
|
||||
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
@api_versions.experimental_api
|
||||
def migration_get_progress(self, share):
|
||||
"""Obtains progress of share migration for a given share.
|
||||
|
@ -621,68 +621,82 @@ def do_create(cs, args):
|
||||
_print_share(cs, share)
|
||||
|
||||
|
||||
@api_versions.wraps("2.22")
|
||||
@cliutils.arg(
|
||||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of share to migrate.')
|
||||
@cliutils.arg(
|
||||
'host',
|
||||
metavar='<host#pool>',
|
||||
help='Destination host and pool.')
|
||||
metavar='<host@backend#pool>',
|
||||
help="Destination host, backend and pool in format 'host@backend#pool'.")
|
||||
@cliutils.arg(
|
||||
'--force-host-copy',
|
||||
'--force_host_copy',
|
||||
'--force_host_assisted_migration',
|
||||
'--force-host-assisted-migration',
|
||||
metavar='<True|False>',
|
||||
choices=['True', 'False'],
|
||||
required=False,
|
||||
action='single_alias',
|
||||
help='Enables or disables generic host-based force-migration, which '
|
||||
'bypasses driver optimizations. Default=False.',
|
||||
'bypasses driver optimizations. Default=False. '
|
||||
'Renamed from "force_host_copy" in version 2.22.',
|
||||
default=False)
|
||||
@api_versions.wraps("2.5", "2.14")
|
||||
def do_migrate(cs, args):
|
||||
"""(Deprecated) Migrates share to a new host (Admin only, Experimental)."""
|
||||
share = _find_share(cs, args.share)
|
||||
share.migration_start(args.host, args.force_host_copy, True)
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of share to migrate.')
|
||||
@cliutils.arg(
|
||||
'host',
|
||||
metavar='<host#pool>',
|
||||
help='Destination host and pool.')
|
||||
@cliutils.arg(
|
||||
'--force-host-copy',
|
||||
'--force_host_copy',
|
||||
'--preserve-metadata',
|
||||
'--preserve_metadata',
|
||||
action='single_alias',
|
||||
metavar='<True|False>',
|
||||
choices=['True', 'False'],
|
||||
required=False,
|
||||
help='Enables or disables generic host-based force-migration, which '
|
||||
'bypasses driver optimizations. Default=False.',
|
||||
default=False)
|
||||
@cliutils.arg(
|
||||
'--notify',
|
||||
metavar='<True|False>',
|
||||
choices=['True', 'False'],
|
||||
required=False,
|
||||
help='Enables or disables notification of data copying completed. '
|
||||
'Default=True.',
|
||||
help='Chooses whether migration should be forced to preserve all file '
|
||||
'metadata when moving its contents. Default=True. '
|
||||
'Introduced in version 2.22.',
|
||||
default=True)
|
||||
@api_versions.wraps("2.15")
|
||||
@cliutils.arg(
|
||||
'--writable',
|
||||
metavar='<True|False>',
|
||||
choices=['True', 'False'],
|
||||
required=False,
|
||||
help='Chooses whether migration should be forced to remain writable '
|
||||
'while contents are being moved. Default=True. '
|
||||
'Introduced in version 2.22.',
|
||||
default=True)
|
||||
@cliutils.arg(
|
||||
'--non-disruptive',
|
||||
'--non_disruptive',
|
||||
action='single_alias',
|
||||
metavar='<True|False>',
|
||||
choices=['True', 'False'],
|
||||
required=False,
|
||||
help='Chooses whether migration should only be performed if it is not '
|
||||
'disruptive. Default=False. Introduced in version 2.22.',
|
||||
default=False)
|
||||
@cliutils.arg(
|
||||
'--new_share_network',
|
||||
'--new-share-network',
|
||||
metavar='<new_share_network>',
|
||||
action='single_alias',
|
||||
required=False,
|
||||
help='Specifies a new share network if desired to change. Default=None. '
|
||||
'Introduced in version 2.22.',
|
||||
default=None)
|
||||
def do_migration_start(cs, args):
|
||||
"""Migrates share to a new host (Admin only, Experimental)."""
|
||||
share = _find_share(cs, args.share)
|
||||
share.migration_start(args.host, args.force_host_copy, args.notify)
|
||||
new_share_net_id = None
|
||||
if args.new_share_network:
|
||||
share_net = _find_share_network(cs, args.new_share_network)
|
||||
new_share_net_id = share_net.id if share_net else None
|
||||
share.migration_start(args.host, args.force_host_assisted_migration,
|
||||
args.preserve_metadata, args.writable,
|
||||
args.non_disruptive, new_share_net_id)
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of share to complete migration.')
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
def do_migration_complete(cs, args):
|
||||
"""Completes migration for a given share (Admin only, Experimental)."""
|
||||
share = _find_share(cs, args.share)
|
||||
@ -693,7 +707,7 @@ def do_migration_complete(cs, args):
|
||||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of share to cancel migration.')
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
def do_migration_cancel(cs, args):
|
||||
"""Cancels migration of a given share when copying
|
||||
|
||||
@ -712,8 +726,9 @@ def do_migration_cancel(cs, args):
|
||||
'--task_state',
|
||||
'--state',
|
||||
metavar='<task_state>',
|
||||
default='migration_error',
|
||||
default='None',
|
||||
action='single_alias',
|
||||
required=False,
|
||||
help=('Indicate which task state to assign the share. Options include '
|
||||
'migration_starting, migration_in_progress, migration_completing, '
|
||||
'migration_success, migration_error, migration_cancelled, '
|
||||
@ -721,15 +736,18 @@ def do_migration_cancel(cs, args):
|
||||
'data_copying_starting, data_copying_in_progress, '
|
||||
'data_copying_completing, data_copying_completed, '
|
||||
'data_copying_cancelled, data_copying_error. If no value is '
|
||||
'provided, migration_error will be used.'))
|
||||
@api_versions.wraps("2.15")
|
||||
'provided, None will be used.'))
|
||||
@api_versions.wraps("2.22")
|
||||
def do_reset_task_state(cs, args):
|
||||
"""Explicitly update the task state of a share
|
||||
|
||||
(Admin only, Experimental).
|
||||
"""
|
||||
state = args.task_state
|
||||
if args.task_state == 'None':
|
||||
state = None
|
||||
share = _find_share(cs, args.share)
|
||||
share.reset_task_state(args.task_state)
|
||||
share.reset_task_state(state)
|
||||
|
||||
|
||||
@cliutils.arg(
|
||||
@ -737,7 +755,7 @@ def do_reset_task_state(cs, args):
|
||||
metavar='<share>',
|
||||
help='Name or ID of the share to get share migration progress '
|
||||
'information.')
|
||||
@api_versions.wraps("2.15")
|
||||
@api_versions.wraps("2.22")
|
||||
def do_migration_get_progress(cs, args):
|
||||
"""Gets migration progress of a given share when copying
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
prelude: >
|
||||
- Added new parameters to Share Migration experimental API.
|
||||
features:
|
||||
- Share Migration now has parameters to force share migration
|
||||
procedure to maintain the share writable, preserve its metadata
|
||||
and be non-disruptive when migrating.
|
||||
- Added parameter to change share network when performing
|
||||
migration.
|
||||
deprecations:
|
||||
- Renamed Share Migration 'force_host_copy' parameter
|
||||
to 'force_host_assisted_migration', to better represent
|
||||
the parameter's functionality.
|
||||
- API version 2.22 is now required for all Share Migration APIs.
|
||||
upgrades:
|
||||
- Removed Share Migration 'notify' parameter, it is no longer
|
||||
possible to perform a 1-phase migration.
|
||||
- Removed 'migrate_share' API support.
|
||||
- Added 'None' to 'reset_task_state' API possible values so it
|
||||
can unset the task_state.
|
Loading…
x
Reference in New Issue
Block a user