Add commands for share server migration

Implements the commands for performing the share server migration.
They are: share-server-migration-reset-task-state,
share-server-migration-complete, share-server-migration-cancel,
share-server-migration-start, share-server-migration-check
and share-server-migration-get-progress.

Co-Authored-By: Daniel Tapia <danielarthurt@gmail.com>
Co-Authored-By: Andre Beltrami <debeltrami@gmail.com>
Co-Authored-By: Douglas Viroel <viroel@gmail.com>

Change-Id: Id829a375d828a4808306fd1a42bc7548721feb19
Partially-implements: bp share-server-migration
Depends-On: Ic0751027d2c3f1ef7ab0f7836baff3070a230cfd
Depends-On: I46a0cee0a4625d950f02fa7a5bf612de926451b5
This commit is contained in:
Felipe Rodrigues 2020-06-26 12:34:01 +00:00 committed by Douglas Viroel
parent 399c0c4fb5
commit edf064a335
14 changed files with 714 additions and 4 deletions

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MAX_VERSION = '2.56' MAX_VERSION = '2.57'
MIN_VERSION = '2.0' MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0' DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {} _VERSIONED_METHOD_MAP = {}

View File

@ -97,6 +97,7 @@ STATUS_MANAGE_ERROR = 'manage_error'
STATUS_UNMANAGE_ERROR = 'unmanage_error' STATUS_UNMANAGE_ERROR = 'unmanage_error'
STATUS_DELETING = 'deleting' STATUS_DELETING = 'deleting'
STATUS_CREATING = 'creating' STATUS_CREATING = 'creating'
STATUS_SERVER_MIGRATING = 'server_migrating'
SNAPSHOT_SUPPORT = 'snapshot_support' SNAPSHOT_SUPPORT = 'snapshot_support'
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = 'create_share_from_snapshot_support' CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = 'create_share_from_snapshot_support'

View File

@ -186,6 +186,11 @@ share_opts = [
help="Defines whether to run manage/unmanage tests or " help="Defines whether to run manage/unmanage tests or "
"not. Disable this feature if used driver does not " "not. Disable this feature if used driver does not "
"support it."), "support it."),
cfg.BoolOpt("run_share_servers_migration_tests",
default=False,
help="Defines whether to run share server migration tests or "
"not. Disable this feature if used driver does not "
"support it."),
] ]

View File

@ -1613,6 +1613,83 @@ class ManilaCLIClient(base.CLIClient):
managed_share = output_parser.details(managed_share_raw) managed_share = output_parser.details(managed_share_raw)
return managed_share['id'] return managed_share['id']
def share_server_migration_check(self, server_id, dest_host, writable,
nondisruptive, preserve_snapshots,
new_share_network=None):
cmd = ('share-server-migration-check %(server_id)s %(host)s '
'--writable %(writable)s --nondisruptive %(nondisruptive)s '
'--preserve-snapshots %(preserve_snapshots)s') % {
'server_id': server_id,
'host': dest_host,
'writable': writable,
'nondisruptive': nondisruptive,
'preserve_snapshots': preserve_snapshots,
}
if new_share_network:
cmd += ' --new-share-network %s' % new_share_network
result = self.manila(cmd)
return output_parser.details(result)
def share_server_migration_start(self, server_id, dest_host,
writable=False, nondisruptive=False,
preserve_snapshots=False,
new_share_network=None):
cmd = ('share-server-migration-start %(server_id)s %(host)s '
'--writable %(writable)s --nondisruptive %(nondisruptive)s '
'--preserve-snapshots %(preserve_snapshots)s') % {
'server_id': server_id,
'host': dest_host,
'writable': writable,
'nondisruptive': nondisruptive,
'preserve_snapshots': preserve_snapshots,
}
if new_share_network:
cmd += ' --new-share-network %s' % new_share_network
return self.manila(cmd)
def share_server_migration_complete(self, server_id):
return self.manila('share-server-migration-complete %s' % server_id)
def share_server_migration_cancel(self, server_id):
return self.manila('share-server-migration-cancel %s' % server_id)
def share_server_migration_get_progress(self, server_id):
result = self.manila('share-server-migration-get-progress %s'
% server_id)
return output_parser.details(result)
def wait_for_server_migration_task_state(self, share_server_id, dest_host,
task_state_to_wait,
microversion=None):
"""Waits for a certain server task state. """
statuses = ((task_state_to_wait,)
if not isinstance(task_state_to_wait, (tuple, list, set))
else task_state_to_wait)
server = self.get_share_server(share_server=share_server_id,
microversion=microversion)
start = int(time.time())
while server['task_state'] not in statuses:
time.sleep(self.build_interval)
server = self.get_share_server(share_server=share_server_id,
microversion=microversion)
if server['task_state'] in statuses:
return server
elif server['task_state'] == constants.TASK_STATE_MIGRATION_ERROR:
raise exceptions.ShareServerMigrationException(
server_id=server['id'])
elif int(time.time()) - start >= self.build_timeout:
message = ('Server %(share_server_id)s failed to reach the '
'status in %(status)s while migrating from host '
'%(src)s to host %(dest)s within the required time '
'%(timeout)s.' % {
'src': server['host'],
'dest': dest_host,
'share_server_id': server['id'],
'timeout': self.build_timeout,
'status': six.text_type(statuses),
})
raise tempest_lib_exc.TimeoutException(message)
# user messages # user messages
def wait_for_message(self, resource_id): def wait_for_message(self, resource_id):

View File

@ -64,3 +64,7 @@ class AccessRuleDeleteErrorException(exceptions.TempestException):
class ShareMigrationException(exceptions.TempestException): class ShareMigrationException(exceptions.TempestException):
message = ("Share %(share_id)s failed to migrate from " message = ("Share %(share_id)s failed to migrate from "
"host %(src)s to host %(dest)s.") "host %(src)s to host %(dest)s.")
class ShareServerMigrationException(exceptions.TempestException):
message = ("Share server %(server_id)s failed to migrate.")

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ast
import ddt import ddt
import testtools import testtools
@ -70,12 +71,11 @@ class ShareServersReadWriteBase(base.BaseTestCase):
def setUp(self): def setUp(self):
super(ShareServersReadWriteBase, self).setUp() super(ShareServersReadWriteBase, self).setUp()
if not CONF.run_share_servers_tests: if not CONF.run_share_servers_tests:
message = "share-servers tests are disabled." message = "share-server tests are disabled."
raise self.skipException(message) raise self.skipException(message)
if self.protocol not in CONF.enable_protocols: if self.protocol not in CONF.enable_protocols:
message = "%s tests are disabled." % self.protocol message = "%s tests are disabled." % self.protocol
raise self.skipException(message) raise self.skipException(message)
self.client = self.get_admin_client() self.client = self.get_admin_client()
if not self.client.share_network: if not self.client.share_network:
message = "Can run only with DHSS=True mode" message = "Can run only with DHSS=True mode"
@ -204,10 +204,152 @@ class ShareServersReadWriteCIFSTest(ShareServersReadWriteBase):
protocol = 'cifs' protocol = 'cifs'
@ddt.ddt
@utils.skip_if_microversion_not_supported('2.57')
class ShareServersMigrationBase(base.BaseTestCase):
protocol = None
def setUp(self):
super(ShareServersMigrationBase, self).setUp()
if not CONF.run_share_servers_tests:
message = "Share-server tests are disabled."
raise self.skipException(message)
if self.protocol not in CONF.enable_protocols:
message = "%s tests are disabled." % self.protocol
raise self.skipException(message)
self.client = self.get_admin_client()
if not self.client.share_network:
message = "Can run only with DHSS=True mode"
raise self.skipException(message)
if not CONF.run_share_servers_migration_tests:
message = "Share server migration tests are disabled."
raise self.skipException(message)
def _create_share_and_share_network(self):
name = data_utils.rand_name('autotest_share_name')
description = data_utils.rand_name('autotest_share_description')
common_share_network = self.client.get_share_network(
self.client.share_network)
share_net_info = utils.get_default_subnet(self.client,
common_share_network['id'])
neutron_net_id = (
share_net_info['neutron_net_id']
if 'none' not in share_net_info['neutron_net_id'].lower()
else None)
neutron_subnet_id = (
share_net_info['neutron_subnet_id']
if 'none' not in share_net_info['neutron_subnet_id'].lower()
else None)
share_network = self.client.create_share_network(
neutron_net_id=neutron_net_id,
neutron_subnet_id=neutron_subnet_id,
)
share_type = self.create_share_type(
data_utils.rand_name('test_share_type'),
driver_handles_share_servers=True)
share = self.create_share(
share_protocol=self.protocol,
size=1,
name=name,
description=description,
share_type=share_type['ID'],
share_network=share_network['id'],
client=self.client,
wait_for_creation=True
)
share = self.client.get_share(share['id'])
return share, share_network
@ddt.data('cancel', 'complete')
def test_share_server_migration(self, operation):
# Create a share and share network to be used in the tests.
share, share_network = self._create_share_and_share_network()
share_server_id = share['share_server_id']
src_host = share['host'].split('#')[0]
pools = self.admin_client.pool_list(detail=True)
host_list = list()
# Filter the backends DHSS True and different
# than the source host.
for hosts in pools:
host_name = hosts['Name'].split('#')[0]
if (ast.literal_eval(hosts['Capabilities']).get(
'driver_handles_share_servers') and
host_name != src_host):
host_list.append(host_name)
host_list = list(set(host_list))
# If not found any host we need skip the test.
if len(host_list) == 0:
raise self.skipException("No hosts available for "
"share server migration.")
dest_backend = None
# If found at least one host, we still need to verify the
# share server migration compatibility with the destination host.
for host in host_list:
compatibility = self.admin_client.share_server_migration_check(
server_id=share_server_id, dest_host=host,
writable=False, nondisruptive=False, preserve_snapshots=False,
new_share_network=None)
# If found at least one compatible host, we will use it.
if compatibility['compatible']:
dest_host = host
# If not found, we need skip the test.
if dest_backend is not None:
raise self.skipException("No hosts compatible to perform a "
"share server migration.")
# Start the share server migration
self.admin_client.share_server_migration_start(
share_server_id, dest_host)
server = self.admin_client.get_share_server(share_server_id)
share = self.admin_client.get_share(share['id'])
self.assertEqual(constants.STATUS_SERVER_MIGRATING, share['status'])
# Wait for the share server migration driver phase 1 done.
task_state = constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE
server = self.admin_client.wait_for_server_migration_task_state(
share_server_id, dest_host, task_state)
# Call share server migration complete or cancel operations
# according the ddt.
if operation == 'complete':
self.admin_client.share_server_migration_complete(
share_server_id)
task_state = constants.TASK_STATE_MIGRATION_SUCCESS
else:
self.admin_client.share_server_migration_cancel(server['id'])
task_state = constants.TASK_STATE_MIGRATION_CANCELLED
# Wait for the respectives task state for each operation above.
server = self.admin_client.wait_for_server_migration_task_state(
server['id'], dest_host, task_state)
# Check if the share is available again.
share = self.admin_client.get_share(share['id'])
self.assertEqual('available', share['status'])
class ShareServersMigrationNFSTest(ShareServersMigrationBase):
protocol = 'nfs'
class ShareServersMigrationCIFSTest(ShareServersMigrationBase):
protocol = 'cifs'
def load_tests(loader, tests, _): def load_tests(loader, tests, _):
result = [] result = []
for test_case in tests: for test_case in tests:
if type(test_case._tests[0]) is ShareServersReadWriteBase: if type(test_case._tests[0]) is ShareServersReadWriteBase:
continue continue
if type(test_case._tests[0]) is ShareServersMigrationBase:
continue
result.append(test_case) result.append(test_case)
return loader.suiteClass(result) return loader.suiteClass(result)

View File

@ -454,6 +454,33 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
} }
elif action in ('unmanage', ): elif action in ('unmanage', ):
assert 'force' in body[action] assert 'force' in body[action]
elif action in (
'migration_cancel', 'migration_complete',
'migration_get_progress'):
assert body[action] is None
if 'migration_get_progress' == action:
_body = {'total_progress': 50,
'task_state': 'fake_task_state',
'destination_share_server_id': 'fake_dest_id'}
return 200, {}, _body
elif 'migration_complete' == action:
_body = {'destination_share_server_id': 'fake_dest_id'}
return 200, {}, _body
elif action in (
'migration_start', 'migration_check'):
assert 'host' in body[action]
if 'migration-check':
_body = {
'compatible': True,
'capacity': True,
'capability': True,
'writable': True,
'nondisruptive': True,
'preserve_snapshots': True,
}
return 200, {}, _body
elif action == 'reset_task_state':
assert 'task_state' in body[action]
resp = 202 resp = 202
result = (resp, {}, _body) result = (resp, {}, _body)

View File

@ -201,3 +201,101 @@ class ShareServerManagerTest(utils.TestCase):
self.manager._get.assert_called_once_with( self.manager._get.assert_called_once_with(
"%s/%s/details" % (share_servers.RESOURCES_PATH, "%s/%s/details" % (share_servers.RESOURCES_PATH,
share_server_id), 'details') share_server_id), 'details')
def test_migration_check(self):
share_server = "fake_share_server"
host = "fake_host"
returned = {
'compatible': True,
'capacity': True,
'capability': True,
'writable': True,
'nondisruptive': True,
'preserve_snapshots': True,
}
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=['200', returned])):
result = self.manager.migration_check(
share_server, host, writable=True, nondisruptive=True,
preserve_snapshots=True)
self.manager._action.assert_called_once_with(
'migration_check', share_server, {
"host": host,
"writable": True,
"nondisruptive": True,
"preserve_snapshots": True,
"new_share_network_id": None,
})
self.assertEqual(returned, result)
def test_migration_start(self):
share_server = "fake_share_server"
host = "fake_host"
returned = "fake"
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=returned)):
result = self.manager.migration_start(
share_server, host, writable=True, nondisruptive=True,
preserve_snapshots=True)
self.manager._action.assert_called_once_with(
'migration_start', share_server, {
"host": host,
"writable": True,
"nondisruptive": True,
"preserve_snapshots": True,
"new_share_network_id": None,
})
self.assertEqual(returned, result)
def test_migration_complete(self):
share_server = "fake_share_server"
returned = "fake"
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=['200', returned])):
result = self.manager.migration_complete(share_server)
self.manager._action.assert_called_once_with(
"migration_complete", share_server)
self.assertEqual(returned, result)
def test_migration_get_progress(self):
share_server = "fake_share_server"
returned = "fake"
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=['200', returned])):
result = self.manager.migration_get_progress(share_server)
self.manager._action.assert_called_once_with(
"migration_get_progress", share_server)
self.assertEqual(returned, result)
def test_reset_task_state(self):
share_server = "fake_share_server"
state = "fake_state"
returned = "fake"
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=returned)):
result = self.manager.reset_task_state(share_server, state)
self.manager._action.assert_called_once_with(
"reset_task_state", share_server, {'task_state': state})
self.assertEqual(returned, result)
def test_migration_cancel(self):
share_server = "fake_share_server"
returned = "fake"
with mock.patch.object(self.manager, "_action",
mock.Mock(return_value=returned)):
result = self.manager.migration_cancel(share_server)
self.manager._action.assert_called_once_with(
"migration_cancel", share_server)
self.assertEqual(returned, result)

View File

@ -3464,3 +3464,38 @@ class ShellTest(test_utils.TestCase):
expected = {'unmanage': {'force': False}} expected = {'unmanage': {'force': False}}
self.assert_called('POST', '/share-servers/1234/action', self.assert_called('POST', '/share-servers/1234/action',
body=expected) body=expected)
@ddt.data('migration-start', 'migration-check')
def test_share_server_migration_start_and_check(self, method):
command = ("share-server-%s "
"1234 host@backend --new-share-network 1111 "
"--writable False --nondisruptive True "
"--preserve-snapshots True" %
method)
self.run_command(command)
method = method.replace('-', '_')
expected = {method: {
'host': 'host@backend',
'writable': 'False',
'nondisruptive': 'True',
'preserve_snapshots': 'True',
'new_share_network_id': 1111
}}
self.assert_called('POST', '/share-servers/1234/action', body=expected)
@ddt.data('migration-complete', 'migration-get-progress',
'migration-cancel')
def test_share_server_migration_others(self, method):
command = 'share-server-' + ' '.join((method, '1234'))
self.run_command(command)
expected = {method.replace('-', '_'): None}
self.assert_called('POST', '/share-servers/1234/action', body=expected)
@ddt.data('migration_error', 'migration_success', None)
def test_share_server_reset_task_state(self, param):
command = ' '.join(('share-server-reset-task-state --state',
six.text_type(param),
'1234'))
self.run_command(command)
expected = {'reset_task_state': {'task_state': param}}
self.assert_called('POST', '/share-servers/1234/action', body=expected)

View File

@ -46,6 +46,36 @@ class ShareServer(common_base.Resource):
"""Update the share server with the provided state.""" """Update the share server with the provided state."""
self.manager.reset_state(self, state) self.manager.reset_state(self, state)
def migration_check(self, host, writable, nondisruptive,
preserve_snapshots, new_share_network_id=None):
"""Check if the new host is suitable for migration."""
return self.manager.migration_check(
self, host, writable, nondisruptive,
preserve_snapshots, new_share_network_id=new_share_network_id)
def migration_start(self, host, writable, nondisruptive,
preserve_snapshots, new_share_network_id=None):
"""Migrate the share server to a new host."""
self.manager.migration_start(
self, host, writable, nondisruptive,
preserve_snapshots, new_share_network_id=new_share_network_id)
def migration_complete(self):
"""Complete migration of a share server."""
return self.manager.migration_complete(self)
def migration_cancel(self):
"""Attempts to cancel migration of a share server."""
self.manager.migration_cancel(self)
def migration_get_progress(self):
"""Obtain progress of migration of a share server."""
return self.manager.migration_get_progress(self)
def reset_task_state(self, task_state):
"""Reset the task state of a given share server."""
self.manager.reset_task_state(self, task_state)
class ShareServerManager(base.ManagerWithFind): class ShareServerManager(base.ManagerWithFind):
"""Manage :class:`ShareServer` resources.""" """Manage :class:`ShareServer` resources."""
@ -148,9 +178,97 @@ class ShareServerManager(base.ManagerWithFind):
:param action: text with action name. :param action: text with action name.
:param share_server: either share_server object or text with its ID. :param share_server: either share_server object or text with its ID.
:param info: dict with data for specified 'action'. :param info: dict with data for specified 'action'.
:param kwargs: dict with data to be provided for action hooks.
""" """
body = {action: info} body = {action: info}
self.run_hooks('modify_body_for_action', body) self.run_hooks('modify_body_for_action', body)
url = ACTION_PATH % common_base.getid(share_server) url = ACTION_PATH % common_base.getid(share_server)
return self.api.client.post(url, body=body) return self.api.client.post(url, body=body)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def migration_check(self, share_server, host, writable, nondisruptive,
preserve_snapshots, new_share_network_id=None):
"""Check the share server migration to a new host
:param share_server: either share_server object or text with its ID.
:param host: Destination host where share server will be migrated.
:param writable: Enforces migration to keep the shares writable.
:param nondisruptive: Enforces migration to be nondisruptive.
:param preserve_snapshots: Enforces migration to preserve snapshots.
:param new_share_network_id: Specify the new share network id.
"""
result = self._action(
"migration_check", share_server, {
"host": host,
"preserve_snapshots": preserve_snapshots,
"writable": writable,
"nondisruptive": nondisruptive,
"new_share_network_id": new_share_network_id,
})
return result[1]
@api_versions.wraps("2.57")
@api_versions.experimental_api
def migration_start(self, share_server, host, writable,
nondisruptive, preserve_snapshots,
new_share_network_id=None):
"""Migrates share server to a new host
:param share_server: either share_server object or text with its ID.
:param host: Destination host where share server will be migrated.
:param writable: Enforces migration to keep the shares writable.
:param nondisruptive: Enforces migration to be nondisruptive.
:param preserve_snapshots: Enforces migration to preserve snapshots.
:param new_share_network_id: Specify the new share network id.
"""
return self._action(
"migration_start", share_server, {
"host": host,
"writable": writable,
"nondisruptive": nondisruptive,
"preserve_snapshots": preserve_snapshots,
"new_share_network_id": new_share_network_id,
})
@api_versions.wraps("2.57")
@api_versions.experimental_api
def reset_task_state(self, share_server, task_state):
"""Update the provided share server with the provided task state.
:param share_server: either share_server object or text with its ID.
:param task_state: text with new task state to set for share.
"""
return self._action('reset_task_state', share_server,
{"task_state": task_state})
@api_versions.wraps("2.57")
@api_versions.experimental_api
def migration_complete(self, share_server):
"""Completes migration for a given share server.
:param share_server: either share_server object or text with its ID.
"""
result = self._action('migration_complete', share_server)
# NOTE(dviroel): result[0] is response code, result[1] is dict body
return result[1]
@api_versions.wraps("2.57")
@api_versions.experimental_api
def migration_cancel(self, share_server):
"""Attempts to cancel migration for a given share server.
:param share_server: either share_server object or text with its ID.
"""
return self._action('migration_cancel',
share_server)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def migration_get_progress(self, share_server):
"""Obtains progress of share migration for a given share server.
:param share_server: either share_server object or text with its ID.
"""
result = self._action('migration_get_progress', share_server)
# NOTE(felipefutty): result[0] is response code, result[1] is dict body
return result[1]

View File

@ -1001,6 +1001,200 @@ def do_migration_get_progress(cs, args):
cliutils.print_dict(result[1]) cliutils.print_dict(result[1])
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of the share server to check if the migration is possible.')
@cliutils.arg(
'host',
metavar='<host@backend>',
help="Destination to migrate the share server to. Use the format "
"'<node_hostname>@<backend_name>'.")
@cliutils.arg(
'--preserve-snapshots',
'--preserve_snapshots',
action='single_alias',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Set to True if snapshots must be preserved at the migration "
"destination.")
@cliutils.arg(
'--writable',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Set to True if shares associated with the share server must be "
"writable through the first phase of the migration.")
@cliutils.arg(
'--nondisruptive',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Set to True if migration must be non disruptive to clients that are "
"using the shares associated with the share server through both "
"phases of the migration.")
@cliutils.arg(
'--new_share_network',
'--new-share-network',
metavar='<new_share_network>',
action='single_alias',
required=False,
help="New share network to migrate to. Optional, default=None.",
default=None)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_check(cs, args):
"""Check migration compatibility for a share server with desired properties
(Admin only, Experimental).
"""
share_server = _find_share_server(cs, args.share_server_id)
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
result = share_server.migration_check(
args.host, args.writable, args.nondisruptive, args.preserve_snapshots,
new_share_net_id)
cliutils.print_dict(result)
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of the share server to migrate.')
@cliutils.arg(
'host',
metavar='<host@backend>',
help="Destination to migrate the share server to. Use the format "
"'<node_hostname>@<backend_name>'.")
@cliutils.arg(
'--preserve-snapshots',
'--preserve_snapshots',
action='single_alias',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Set to True if snapshots must be preserved at the migration "
"destination.")
@cliutils.arg(
'--writable',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Enforces migration to keep all its shares writable while contents "
"are being moved.")
@cliutils.arg(
'--nondisruptive',
metavar='<True|False>',
choices=['True', 'False'],
required=True,
help="Enforces migration to be nondisruptive.")
@cliutils.arg(
'--new_share_network',
'--new-share-network',
metavar='<new_share_network>',
action='single_alias',
required=False,
help='Specify a new share network for the share server. Do not '
'specify this parameter if the migrating share server has '
'to be retained within its current share network.',
default=None)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_start(cs, args):
"""Migrates share server to a new host (Admin only, Experimental)."""
share_server = _find_share_server(cs, args.share_server_id)
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
share_server.migration_start(args.host, args.writable, args.nondisruptive,
args.preserve_snapshots, new_share_net_id)
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_complete(cs, args):
"""Completes migration for a given share server
(Admin only,Experimental).
"""
share_server = _find_share_server(cs, args.share_server_id)
result = share_server.migration_complete()
cliutils.print_dict(result)
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_cancel(cs, args):
"""Cancels migration of a given share server when copying
(Admin only, Experimental).
"""
share_server = _find_share_server(cs, args.share_server_id)
share_server.migration_cancel()
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of share server to complete migration.')
@cliutils.arg(
'--task-state',
'--task_state',
'--state',
metavar='<task_state>',
default='None',
action='single_alias',
required=False,
help=('Indicate which task state to assign the share server. Options: '
'migration_starting, migration_in_progress, migration_completing, '
'migration_success, migration_error, migration_cancel_in_progress, '
'migration_cancelled, migration_driver_in_progress, '
'migration_driver_phase1_done. If no value is provided, None will '
'be used.'))
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_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_server = _find_share_server(cs, args.share_server_id)
share_server.reset_task_state(state)
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_get_progress(cs, args):
"""Gets migration progress of a given share server when copying
(Admin only, Experimental).
"""
share_server = _find_share_server(cs, args.share_server_id)
result = share_server.migration_get_progress()
cliutils.print_dict(result)
@cliutils.arg( @cliutils.arg(
'share', 'share',
metavar='<share>', metavar='<share>',

View File

@ -0,0 +1,7 @@
---
features:
- |
Added support for performing the share server migration. The new commands
are: `share-server-migration-start`, `share-server-migration-complete`,
`share-server-migration-cancel`, `share-server-migration-get-progress`,
`share-server-migration-check` and `share-server-reset-task-state`.

View File

@ -30,6 +30,7 @@
iniset {{ manilaclient_config }} DEFAULT run_migration_tests true iniset {{ manilaclient_config }} DEFAULT run_migration_tests true
iniset {{ manilaclient_config }} DEFAULT run_manage_tests true iniset {{ manilaclient_config }} DEFAULT run_manage_tests true
iniset {{ manilaclient_config }} DEFAULT run_mount_snapshot_tests true iniset {{ manilaclient_config }} DEFAULT run_mount_snapshot_tests true
iniset {{ manilaclient_config }} DEFAULT run_share_servers_migration_tests true
args: args:
executable: "/bin/bash" executable: "/bin/bash"

View File

@ -32,6 +32,7 @@
MANILA_INSTALL_TEMPEST_PLUGIN_SYSTEMWIDE: false MANILA_INSTALL_TEMPEST_PLUGIN_SYSTEMWIDE: false
MANILA_SERVICE_IMAGE_ENABLED: false MANILA_SERVICE_IMAGE_ENABLED: false
MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL: 1 MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL: 1
MANILA_SERVER_MIGRATION_PERIOD_TASK_INTERVAL: 10
SHARE_DRIVER: manila.tests.share.drivers.dummy.DummyDriver SHARE_DRIVER: manila.tests.share.drivers.dummy.DummyDriver
MANILA_REPLICA_STATE_UPDATE_INTERVAL: 10 MANILA_REPLICA_STATE_UPDATE_INTERVAL: 10
MANILA_ENABLED_BACKENDS: alpha,beta,gamma,delta MANILA_ENABLED_BACKENDS: alpha,beta,gamma,delta