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:
parent
399c0c4fb5
commit
edf064a335
@ -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 = {}
|
||||||
|
@ -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'
|
||||||
|
@ -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."),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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.")
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
@ -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>',
|
||||||
|
@ -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`.
|
@ -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"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user