diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index 285e8e8af..07d73ffa8 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.56' +MAX_VERSION = '2.57' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/common/constants.py b/manilaclient/common/constants.py index 38096db97..291aa3110 100644 --- a/manilaclient/common/constants.py +++ b/manilaclient/common/constants.py @@ -97,6 +97,7 @@ STATUS_MANAGE_ERROR = 'manage_error' STATUS_UNMANAGE_ERROR = 'unmanage_error' STATUS_DELETING = 'deleting' STATUS_CREATING = 'creating' +STATUS_SERVER_MIGRATING = 'server_migrating' SNAPSHOT_SUPPORT = 'snapshot_support' CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = 'create_share_from_snapshot_support' diff --git a/manilaclient/config.py b/manilaclient/config.py index 052557f11..94e414896 100644 --- a/manilaclient/config.py +++ b/manilaclient/config.py @@ -186,6 +186,11 @@ share_opts = [ help="Defines whether to run manage/unmanage tests or " "not. Disable this feature if used driver does not " "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."), ] diff --git a/manilaclient/tests/functional/client.py b/manilaclient/tests/functional/client.py index 15c45a3cf..47f255eab 100644 --- a/manilaclient/tests/functional/client.py +++ b/manilaclient/tests/functional/client.py @@ -1613,6 +1613,83 @@ class ManilaCLIClient(base.CLIClient): managed_share = output_parser.details(managed_share_raw) 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 def wait_for_message(self, resource_id): diff --git a/manilaclient/tests/functional/exceptions.py b/manilaclient/tests/functional/exceptions.py index 0ec49d6ef..fd96a4da0 100644 --- a/manilaclient/tests/functional/exceptions.py +++ b/manilaclient/tests/functional/exceptions.py @@ -64,3 +64,7 @@ class AccessRuleDeleteErrorException(exceptions.TempestException): class ShareMigrationException(exceptions.TempestException): message = ("Share %(share_id)s failed to migrate from " "host %(src)s to host %(dest)s.") + + +class ShareServerMigrationException(exceptions.TempestException): + message = ("Share server %(server_id)s failed to migrate.") diff --git a/manilaclient/tests/functional/test_share_servers.py b/manilaclient/tests/functional/test_share_servers.py index 341548dc8..e422e8cfe 100644 --- a/manilaclient/tests/functional/test_share_servers.py +++ b/manilaclient/tests/functional/test_share_servers.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ast import ddt import testtools @@ -70,12 +71,11 @@ class ShareServersReadWriteBase(base.BaseTestCase): def setUp(self): super(ShareServersReadWriteBase, self).setUp() if not CONF.run_share_servers_tests: - message = "share-servers tests are disabled." + 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" @@ -204,10 +204,152 @@ class ShareServersReadWriteCIFSTest(ShareServersReadWriteBase): 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, _): result = [] for test_case in tests: if type(test_case._tests[0]) is ShareServersReadWriteBase: continue + if type(test_case._tests[0]) is ShareServersMigrationBase: + continue result.append(test_case) return loader.suiteClass(result) diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py index 8ef634b8a..4b5bafeee 100644 --- a/manilaclient/tests/unit/v2/fakes.py +++ b/manilaclient/tests/unit/v2/fakes.py @@ -454,6 +454,33 @@ class FakeHTTPClient(fakes.FakeHTTPClient): } elif action in ('unmanage', ): 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 result = (resp, {}, _body) diff --git a/manilaclient/tests/unit/v2/test_share_servers.py b/manilaclient/tests/unit/v2/test_share_servers.py index 1e8af1b69..b5d07774a 100644 --- a/manilaclient/tests/unit/v2/test_share_servers.py +++ b/manilaclient/tests/unit/v2/test_share_servers.py @@ -201,3 +201,101 @@ class ShareServerManagerTest(utils.TestCase): self.manager._get.assert_called_once_with( "%s/%s/details" % (share_servers.RESOURCES_PATH, 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) diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index c92de6a33..c98a880f1 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -3464,3 +3464,38 @@ class ShellTest(test_utils.TestCase): expected = {'unmanage': {'force': False}} self.assert_called('POST', '/share-servers/1234/action', 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) diff --git a/manilaclient/v2/share_servers.py b/manilaclient/v2/share_servers.py index 1b55d986f..6e2839694 100644 --- a/manilaclient/v2/share_servers.py +++ b/manilaclient/v2/share_servers.py @@ -46,6 +46,36 @@ class ShareServer(common_base.Resource): """Update the share server with the provided 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): """Manage :class:`ShareServer` resources.""" @@ -148,9 +178,97 @@ class ShareServerManager(base.ManagerWithFind): :param action: text with action name. :param share_server: either share_server object or text with its ID. :param info: dict with data for specified 'action'. - :param kwargs: dict with data to be provided for action hooks. """ body = {action: info} self.run_hooks('modify_body_for_action', body) url = ACTION_PATH % common_base.getid(share_server) 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] diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index 75bf617e9..560859e4b 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -1001,6 +1001,200 @@ def do_migration_get_progress(cs, args): cliutils.print_dict(result[1]) +@cliutils.arg( + 'share_server_id', + metavar='', + help='ID of the share server to check if the migration is possible.') +@cliutils.arg( + 'host', + metavar='', + help="Destination to migrate the share server to. Use the format " + "'@'.") +@cliutils.arg( + '--preserve-snapshots', + '--preserve_snapshots', + action='single_alias', + metavar='', + choices=['True', 'False'], + required=True, + help="Set to True if snapshots must be preserved at the migration " + "destination.") +@cliutils.arg( + '--writable', + metavar='', + 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='', + 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='', + 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='', + help='ID of the share server to migrate.') +@cliutils.arg( + 'host', + metavar='', + help="Destination to migrate the share server to. Use the format " + "'@'.") +@cliutils.arg( + '--preserve-snapshots', + '--preserve_snapshots', + action='single_alias', + metavar='', + choices=['True', 'False'], + required=True, + help="Set to True if snapshots must be preserved at the migration " + "destination.") +@cliutils.arg( + '--writable', + metavar='', + choices=['True', 'False'], + required=True, + help="Enforces migration to keep all its shares writable while contents " + "are being moved.") +@cliutils.arg( + '--nondisruptive', + metavar='', + choices=['True', 'False'], + required=True, + help="Enforces migration to be nondisruptive.") +@cliutils.arg( + '--new_share_network', + '--new-share-network', + metavar='', + 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='', + 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='', + 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='', + help='ID of share server to complete migration.') +@cliutils.arg( + '--task-state', + '--task_state', + '--state', + metavar='', + 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='', + 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( 'share', metavar='', diff --git a/releasenotes/notes/support-share-server-migration-9804752270c6b153.yaml b/releasenotes/notes/support-share-server-migration-9804752270c6b153.yaml new file mode 100644 index 000000000..9497e7f22 --- /dev/null +++ b/releasenotes/notes/support-share-server-migration-9804752270c6b153.yaml @@ -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`. diff --git a/roles/populate-manilaclient-config/tasks/main.yaml b/roles/populate-manilaclient-config/tasks/main.yaml index 307a250ce..89e067278 100644 --- a/roles/populate-manilaclient-config/tasks/main.yaml +++ b/roles/populate-manilaclient-config/tasks/main.yaml @@ -30,6 +30,7 @@ iniset {{ manilaclient_config }} DEFAULT run_migration_tests true iniset {{ manilaclient_config }} DEFAULT run_manage_tests true iniset {{ manilaclient_config }} DEFAULT run_mount_snapshot_tests true + iniset {{ manilaclient_config }} DEFAULT run_share_servers_migration_tests true args: executable: "/bin/bash" diff --git a/zuul.d/python-manilaclient-jobs.yaml b/zuul.d/python-manilaclient-jobs.yaml index b0a447fae..967cba711 100644 --- a/zuul.d/python-manilaclient-jobs.yaml +++ b/zuul.d/python-manilaclient-jobs.yaml @@ -32,6 +32,7 @@ MANILA_INSTALL_TEMPEST_PLUGIN_SYSTEMWIDE: false MANILA_SERVICE_IMAGE_ENABLED: false MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL: 1 + MANILA_SERVER_MIGRATION_PERIOD_TASK_INTERVAL: 10 SHARE_DRIVER: manila.tests.share.drivers.dummy.DummyDriver MANILA_REPLICA_STATE_UPDATE_INTERVAL: 10 MANILA_ENABLED_BACKENDS: alpha,beta,gamma,delta