Modify share groups w/ manila-manage's update-host

We can also condense this code a bit to allow
adding more resources who have a "host" attribute.

Closes-Bug: #1895323
Change-Id: I5b750217e019b7bc5281006f63feba8fa06db534
Signed-off-by: Goutham Pacha Ravi <gouthampravi@gmail.com>
This commit is contained in:
Goutham Pacha Ravi 2020-08-31 18:46:01 -08:00
parent 4036ff32bd
commit 370b15b6c6
6 changed files with 160 additions and 86 deletions

View File

@ -375,24 +375,28 @@ class ShareCommands(object):
@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 and share server.
"""Modify the host name associated with resources.
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.
Affects shares, share servers and share groups
"""
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})
servers = db.share_servers_host_update(ctxt, current_host, new_host)
print("Updated host of %(count)s share servers on %(chost)s "
"to %(nhost)s." % {'count': servers, 'chost': current_host,
'nhost': new_host})
updated = db.share_resources_host_update(ctxt, current_host, new_host)
msg = ("Updated host of %(si_count)d share instances, "
"%(sg_count)d share groups and %(ss_count)d share servers on "
"%(chost)s to %(nhost)s.")
msg_args = {
'si_count': updated['instances'],
'sg_count': updated['groups'],
'ss_count': updated['servers'],
'chost': current_host,
'nhost': new_host,
}
print(msg % msg_args)
CATEGORIES = {

View File

@ -339,11 +339,6 @@ def share_instances_status_update(context, share_instance_ids, values):
context, share_instance_ids, values)
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)
@ -1026,11 +1021,6 @@ def share_server_backend_details_set(context, share_server_id, server_details):
server_details)
def share_servers_host_update(context, current_host, new_host):
"""Update the host attr of all share servers that are on current_host."""
return IMPL.share_servers_host_update(context, current_host, new_host)
##################
@ -1304,6 +1294,11 @@ def share_group_snapshot_member_update(context, member_id, values):
return IMPL.share_group_snapshot_member_update(context, member_id, values)
def share_resources_host_update(context, current_host, new_host):
"""Update the host attr of all share resources that are on current_host."""
return IMPL.share_resources_host_update(context, current_host, new_host)
####################
def share_replicas_get_all(context, with_share_server=False,

View File

@ -389,6 +389,33 @@ QUOTA_SYNC_FUNCTIONS = {
}
###################
@require_admin_context
def share_resources_host_update(context, current_host, new_host):
"""Updates the 'host' attribute of resources"""
resources = {
'instances': models.ShareInstance,
'servers': models.ShareServer,
'groups': models.ShareGroup,
}
result = {}
session = get_session()
with session.begin():
for res_name, res_model in resources.items():
host_field = res_model.host
query = model_query(
context, res_model, session=session, read_deleted="no",
).filter(host_field.like('{}%'.format(current_host)))
count = query.update(
{host_field: func.replace(host_field, current_host, new_host)},
synchronize_session=False)
result.update({res_name: count})
return result
###################
@ -1395,20 +1422,6 @@ 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):
@ -4051,20 +4064,6 @@ def share_server_backend_details_delete(context, share_server_id,
item.soft_delete(session)
@require_admin_context
def share_servers_host_update(context, current_host, new_host):
session = get_session()
host_field = models.ShareServer.host
with session.begin():
query = model_query(
context, models.ShareServer, 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
###################
def _driver_private_data_query(session, context, entity_id, key=None,

View File

@ -404,13 +404,13 @@ class ManilaCmdManageTestCase(test.TestCase):
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.mock_object(db, 'share_resources_host_update')
self.assertRaises(SystemExit,
self.share_cmds.update_host,
current_host, new_host)
self.assertFalse(db.share_instances_host_update.called)
self.assertFalse(db.share_resources_host_update.called)
@ddt.data({'current_host': 'controller-0@fancystore01#pool100',
'new_host': 'controller-0@fancystore02#pool0'},
@ -422,23 +422,18 @@ class ManilaCmdManageTestCase(test.TestCase):
'new_host': 'controller-1@fancystore02', 'force': True})
@ddt.unpack
def test_share_update_host(self, current_host, new_host, force=False):
db_op = {'instances': 3, 'groups': 4, 'servers': 2}
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))
self.mock_object(db, 'share_servers_host_update',
mock.Mock(return_value=5))
self.mock_object(db, 'share_resources_host_update',
mock.Mock(return_value=db_op))
with mock.patch('sys.stdout', new=six.StringIO()) as intercepted_op:
self.share_cmds.update_host(current_host, new_host, force)
expected_op_si = ("Updated host of 20 share instances on "
"%(chost)s to %(nhost)s." %
expected_op = ("Updated host of 3 share instances, 4 share groups and "
"2 share servers on %(chost)s to %(nhost)s." %
{'chost': current_host, 'nhost': new_host})
expected_op_sv = ("Updated host of 5 share servers on "
"%(chost)s to %(nhost)s." %
{'chost': current_host, 'nhost': new_host})
self.assertEqual(expected_op_si + "\n" + expected_op_sv,
intercepted_op.getvalue().strip())
db.share_instances_host_update.assert_called_once_with(
self.assertEqual(expected_op, intercepted_op.getvalue().strip())
db.share_resources_host_update.assert_called_once_with(
'admin_ctxt', current_host, new_host)

View File

@ -4003,16 +4003,18 @@ class BackendInfoDatabaseAPITestCase(test.TestCase):
@ddt.ddt
class ShareInstancesTestCase(test.TestCase):
class ShareResourcesAPITestCase(test.TestCase):
def setUp(self):
super(ShareInstancesTestCase, self).setUp()
super(ShareResourcesAPITestCase, 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):
def test_share_resources_host_update_no_matches(self, current_host):
share_id = uuidutils.generate_uuid()
share_network_id = uuidutils.generate_uuid()
share_network_subnet_id = uuidutils.generate_uuid()
if '@' in current_host:
if '#' in current_host:
new_host = 'new-controller-X@backendX#poolX'
@ -4020,7 +4022,8 @@ class ShareInstancesTestCase(test.TestCase):
new_host = 'new-controller-X@backendX'
else:
new_host = 'new-controller-X'
instances = [
resources = [ # noqa
# share instances
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@fancystore01#pool100',
@ -4033,28 +4036,71 @@ class ShareInstancesTestCase(test.TestCase):
share_id=share_id,
host='controller-2@beststore07#pool200',
status=constants.STATUS_DELETING),
]
db_utils.create_share(id=share_id, instances=instances)
# share groups
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-0@fancystore01#pool200',
status=constants.STATUS_AVAILABLE),
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-0@otherstore02#pool100',
status=constants.STATUS_ERROR),
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-2@beststore07#pool100',
status=constants.STATUS_DELETING),
# share servers
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_id,
host='controller-0@fancystore01',
status=constants.STATUS_ACTIVE),
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_id,
host='controller-0@otherstore02#pool100',
status=constants.STATUS_ERROR),
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_id,
host='controller-2@beststore07',
status=constants.STATUS_DELETING),
updates = db_api.share_instances_host_update(self.context,
]
updates = db_api.share_resources_host_update(self.context,
current_host,
new_host)
expected_updates = {'instances': 0, 'servers': 0, 'groups': 0}
self.assertDictMatch(expected_updates, updates)
# validate that resources are unmodified:
share_instances = db_api.share_instances_get_all(
self.context, filters={'share_id': share_id})
self.assertEqual(0, updates)
share_groups = db_api.share_group_get_all(
self.context, filters={'share_network_id': share_network_id})
share_servers = db_api._server_get_query(self.context).filter_by(
share_network_subnet_id=share_network_subnet_id).all()
self.assertEqual(3, len(share_instances))
self.assertEqual(3, len(share_groups))
self.assertEqual(3, len(share_servers))
for share_instance in share_instances:
self.assertTrue(not share_instance['host'].startswith(new_host))
for share_group in share_groups:
self.assertTrue(not share_group['host'].startswith(new_host))
for share_server in share_servers:
self.assertTrue(not share_server['host'].startswith(new_host))
@ddt.data({'current_host': 'controller-2', 'expected_updates': 1},
@ddt.data(
{'current_host': 'controller-2',
'expected_updates': {'instances': 1, 'servers': 2, 'groups': 1}},
{'current_host': 'controller-0@fancystore01',
'expected_updates': 2},
'expected_updates': {'instances': 2, 'servers': 1, 'groups': 2}},
{'current_host': 'controller-0@fancystore01#pool100',
'expected_updates': 1})
'expected_updates': {'instances': 1, 'servers': 1, 'groups': 0}})
@ddt.unpack
def test_share_instance_host_update_partial_matches(self, current_host,
def test_share_resources_host_update_partial_matches(self, current_host,
expected_updates):
share_id = uuidutils.generate_uuid()
share_network_id = uuidutils.generate_uuid()
share_network_subnet_id = uuidutils.generate_uuid()
if '@' in current_host:
if '#' in current_host:
new_host = 'new-controller-X@backendX#poolX'
@ -4062,7 +4108,11 @@ class ShareInstancesTestCase(test.TestCase):
new_host = 'new-controller-X@backendX'
else:
new_host = 'new-controller-X'
instances = [
total_updates_expected = (expected_updates['instances']
+ expected_updates['groups']
+ expected_updates['servers'])
resources = [ # noqa
# share instances
db_utils.create_share_instance(
share_id=share_id,
host='controller-0@fancystore01#pool100',
@ -4075,19 +4125,50 @@ class ShareInstancesTestCase(test.TestCase):
share_id=share_id,
host='controller-2@beststore07#pool200',
status=constants.STATUS_DELETING),
# share groups
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-0@fancystore01#pool101',
status=constants.STATUS_ACTIVE),
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-0@fancystore01#pool101',
status=constants.STATUS_ERROR),
db_utils.create_share_group(
share_network_id=share_network_id,
host='controller-2@beststore07#pool200',
status=constants.STATUS_DELETING),
# share servers
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_id,
host='controller-0@fancystore01#pool100',
status=constants.STATUS_ACTIVE),
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_id,
host='controller-2@fancystore01',
status=constants.STATUS_ERROR),
db_utils.create_share_server(
share_network_subnet_id=share_network_subnet_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(
actual_updates = db_api.share_resources_host_update(
self.context, current_host, new_host)
share_instances = db_api.share_instances_get_all(
self.context, filters={'share_id': share_id})
share_groups = db_api.share_group_get_all(
self.context, filters={'share_network_id': share_network_id})
share_servers = db_api._server_get_query(self.context).filter_by(
share_network_subnet_id=share_network_subnet_id).all()
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))
updated_resources = [
res for res in share_instances + share_groups + share_servers
if res['host'].startswith(new_host)
]
self.assertEqual(expected_updates, actual_updates)
self.assertEqual(total_updates_expected, len(updated_resources))
def test_share_instances_status_update(self):
for i in range(1, 3):

View File

@ -2,4 +2,4 @@
fixes:
- |
The ``manila-manage share update_host`` command now updates the host
attribute of share servers in addition to shares.
attribute of share servers and share groups in addition to shares.