Add command to update share instance hosts

usage: manila-manage share update_host --currenthost CURRENTHOST
                                       --newhost NEWHOST
                                        [--force FORCE]
  --currenthost CURRENTHOST  Current share host name.
  --newhost NEWHOST     New share host name.
  --force FORCE         Ignore validations.

A fully qualified host string is of the format
'HostA@BackendB#PoolC'. The command allows making
substring modifications to update the host or the
host and backend. To avoid making gratuitous changes,
the complete host string must be specified.

Change-Id: I0f30ab6135c8c438860341cc68b512311e9c4711
This commit is contained in:
Goutham Pacha Ravi 2018-06-29 16:39:52 -07:00
parent 94db9c7efa
commit dbf97098e1
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.