Merge "Add command to update share instance hosts"

This commit is contained in:
Zuul 2018-10-03 16:06:29 +00:00 committed by Gerrit Code Review
commit f4eb29207e
6 changed files with 201 additions and 0 deletions

View File

@ -75,6 +75,16 @@ from manila import version
CONF = cfg.CONF
HOST_UPDATE_HELP_MSG = ("A fully qualified host string is of the format "
"'HostA@BackendB#PoolC'. Provide only the host name "
"(ex: 'HostA') to update the hostname part of "
"the host string. Provide only the "
"host name and backend name (ex: 'HostA@BackendB') to "
"update the host and backend names.")
HOST_UPDATE_CURRENT_HOST_HELP = ("Current share host name. %s" %
HOST_UPDATE_HELP_MSG)
HOST_UPDATE_NEW_HOST_HELP = "New share host name. %s" % HOST_UPDATE_HELP_MSG
# Decorators for actions
def args(*args, **kwargs):
@ -355,12 +365,47 @@ class ServiceCommands(object):
))
class ShareCommands(object):
@staticmethod
def _validate_hosts(current_host, new_host):
err = None
if '@' in current_host:
if '#' in current_host and '#' not in new_host:
err = "%(chost)s specifies a pool but %(nhost)s does not."
elif '@' not in new_host:
err = "%(chost)s specifies a backend but %(nhost)s does not."
if err:
print(err % {'chost': current_host, 'nhost': new_host})
sys.exit(1)
@args('--currenthost', required=True, help=HOST_UPDATE_CURRENT_HOST_HELP)
@args('--newhost', required=True, help=HOST_UPDATE_NEW_HOST_HELP)
@args('--force', required=False, type=bool, default=False,
help="Ignore validations.")
def update_host(self, current_host, new_host, force=False):
"""Modify the host name associated with a share.
Particularly to recover from cases where one has moved
their Manila Share node, or modified their 'host' opt
or their backend section name in the manila configuration file.
"""
if not force:
self._validate_hosts(current_host, new_host)
ctxt = context.get_admin_context()
updated = db.share_instances_host_update(ctxt, current_host, new_host)
print("Updated host of %(count)s share instances on %(chost)s "
"to %(nhost)s." % {'count': updated, 'chost': current_host,
'nhost': new_host})
CATEGORIES = {
'config': ConfigCommands,
'db': DbCommands,
'host': HostCommands,
'logs': GetLogCommands,
'service': ServiceCommands,
'share': ShareCommands,
'shell': ShellCommands,
'version': VersionCommands
}

View File

@ -325,6 +325,11 @@ def share_instance_update(context, instance_id, values, with_share_data=False):
with_share_data=with_share_data)
def share_instances_host_update(context, current_host, new_host):
"""Update the host attr of all share instances that are on current_host."""
return IMPL.share_instances_host_update(context, current_host, new_host)
def share_instances_get_all(context, filters=None):
"""Returns all share instances."""
return IMPL.share_instances_get_all(context, filters=filters)

View File

@ -1339,6 +1339,20 @@ def _share_instance_create(context, share_id, values, session):
session=session)
@require_admin_context
def share_instances_host_update(context, current_host, new_host):
session = get_session()
host_field = models.ShareInstance.host
with session.begin():
query = model_query(
context, models.ShareInstance, session=session, read_deleted="no",
).filter(host_field.like('{}%'.format(current_host)))
result = query.update(
{host_field: func.replace(host_field, current_host, new_host)},
synchronize_session=False)
return result
@require_context
def share_instance_update(context, share_instance_id, values,
with_share_data=False):

View File

@ -45,6 +45,7 @@ class ManilaCmdManageTestCase(test.TestCase):
self.config_commands = manila_manage.ConfigCommands()
self.get_log_cmds = manila_manage.GetLogCommands()
self.service_cmds = manila_manage.ServiceCommands()
self.share_cmds = manila_manage.ShareCommands()
def test_param2id_is_uuid_like(self):
obj_id = '12345678123456781234567812345678'
@ -376,3 +377,44 @@ class ManilaCmdManageTestCase(test.TestCase):
def test_get_arg_string(self, arg):
parsed_arg = manila_manage.get_arg_string(arg)
self.assertEqual('bar', parsed_arg)
@ddt.data({'current_host': 'controller-0@fancystore01#pool100',
'new_host': 'controller-0@fancystore01'},
{'current_host': 'controller-0@fancystore01',
'new_host': 'controller-0'})
@ddt.unpack
def test_share_update_host_fail_validation(self, current_host, new_host):
self.mock_object(context, 'get_admin_context',
mock.Mock(return_value='admin_ctxt'))
self.mock_object(db, 'share_instances_host_update')
self.assertRaises(SystemExit,
self.share_cmds.update_host,
current_host, new_host)
self.assertFalse(db.share_instances_host_update.called)
@ddt.data({'current_host': 'controller-0@fancystore01#pool100',
'new_host': 'controller-0@fancystore02#pool0'},
{'current_host': 'controller-0@fancystore01',
'new_host': 'controller-1@fancystore01'},
{'current_host': 'controller-0',
'new_host': 'controller-1'},
{'current_host': 'controller-0@fancystore01#pool100',
'new_host': 'controller-1@fancystore02', 'force': True})
@ddt.unpack
def test_share_update_host(self, current_host, new_host, force=False):
self.mock_object(context, 'get_admin_context',
mock.Mock(return_value='admin_ctxt'))
self.mock_object(db, 'share_instances_host_update',
mock.Mock(return_value=20))
with mock.patch('sys.stdout', new=six.StringIO()) as intercepted_op:
self.share_cmds.update_host(current_host, new_host, force)
expected_op = ("Updated host of 20 share instances on "
"%(chost)s to %(nhost)s." %
{'chost': current_host, 'nhost': new_host})
self.assertEqual(expected_op, intercepted_op.getvalue().strip())
db.share_instances_host_update.assert_called_once_with(
'admin_ctxt', current_host, new_host)

View File

@ -3133,3 +3133,91 @@ class BackendInfoDatabaseAPITestCase(test.TestCase):
self.assertIsNone(initial_data)
self.assertEqual(value_2, actual_data['info_hash'])
self.assertEqual(host, actual_data['host'])
@ddt.ddt
class ShareInstancesTestCase(test.TestCase):
def setUp(self):
super(ShareInstancesTestCase, self).setUp()
self.context = context.get_admin_context()
@ddt.data('controller-100', 'controller-0@otherstore03',
'controller-0@otherstore01#pool200')
def test_share_instances_host_update_no_matches(self, current_host):
share_id = uuidutils.generate_uuid()
if '@' in current_host:
if '#' in current_host:
new_host = 'new-controller-X@backendX#poolX'
else:
new_host = 'new-controller-X@backendX'
else:
new_host = 'new-controller-X'
instances = [
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@fancystore01#pool100',
status=constants.STATUS_AVAILABLE),
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@otherstore02#pool100',
status=constants.STATUS_ERROR),
db_utils.create_share_instance(
share_id=share_id,
host='controller-2@beststore07#pool200',
status=constants.STATUS_DELETING),
]
db_utils.create_share(id=share_id, instances=instances)
updates = db_api.share_instances_host_update(self.context,
current_host,
new_host)
share_instances = db_api.share_instances_get_all(
self.context, filters={'share_id': share_id})
self.assertEqual(0, updates)
for share_instance in share_instances:
self.assertTrue(not share_instance['host'].startswith(new_host))
@ddt.data({'current_host': 'controller-2', 'expected_updates': 1},
{'current_host': 'controller-0@fancystore01',
'expected_updates': 2},
{'current_host': 'controller-0@fancystore01#pool100',
'expected_updates': 1})
@ddt.unpack
def test_share_instance_host_update_partial_matches(self, current_host,
expected_updates):
share_id = uuidutils.generate_uuid()
if '@' in current_host:
if '#' in current_host:
new_host = 'new-controller-X@backendX#poolX'
else:
new_host = 'new-controller-X@backendX'
else:
new_host = 'new-controller-X'
instances = [
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@fancystore01#pool100',
status=constants.STATUS_AVAILABLE),
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@fancystore01#pool200',
status=constants.STATUS_ERROR),
db_utils.create_share_instance(
share_id=share_id,
host='controller-2@beststore07#pool200',
status=constants.STATUS_DELETING),
]
db_utils.create_share(id=share_id, instances=instances)
actual_updates = db_api.share_instances_host_update(
self.context, current_host, new_host)
share_instances = db_api.share_instances_get_all(
self.context, filters={'share_id': share_id})
host_updates = [si for si in share_instances if
si['host'].startswith(new_host)]
self.assertEqual(actual_updates, expected_updates)
self.assertEqual(expected_updates, len(host_updates))

View File

@ -0,0 +1,7 @@
---
features:
- The ``manila-manage`` utility now has a new command to update the host
attribute of shares. This is useful when the share manager process
has been migrated to a different host, or if changes are made to the
``host`` config option or the backend section name in ``manila.conf``.
Execute ``manila-manage share update_host -h`` to see usage instructions.