Improve the client performance on large clouds

This patch fixes the client performance when used on large clouds.
Previously the client was pulling down a full list of resources when
mapping names to IDs and filtering on the client side. This is inefficient
as it is transfering many more records than are necessary.
This patch uses the filter capabilities of the OpenStack APIs to target
the search for a resource ID.

Change-Id: I6e68113aea9ce27beb6691bd2a398bf276a23ae2
(cherry picked from commit 84e927d4bb)
This commit is contained in:
Michael Johnson 2021-05-21 18:58:16 +00:00
parent 0af3511841
commit c845ae44d2
9 changed files with 88 additions and 29 deletions

View File

@ -15,6 +15,7 @@
from osc_lib import exceptions
from openstackclient.identity import common as identity_common
from oslo_utils import uuidutils
def _map_attrs(args, source_attr_map):
@ -58,6 +59,62 @@ def _map_attrs(args, source_attr_map):
return res
def _find_resource(list_funct, resource_name, root_tag, name, parent=None):
"""Search for a resource by name and ID.
This function will search for a resource by both the name and ID,
returning the resource once it finds a match. If no match is found,
an exception will be raised.
:param list_funct: The resource list method to call during searches.
:param resource_name: The name of the resource type we are searching for.
:param root_tag: The root tag of the resource returned from the API.
:param name: The value we are searching for, a resource name or ID.
:param parent: The parent resource ID, when required.
:return: The resource found for the name or ID.
:raises exceptions.CommandError: If more than one match or none are found.
"""
if parent:
parent_args = [parent]
else:
parent_args = []
# Optimize the API call order if we got a UUID-like name or not
if uuidutils.is_uuid_like(name):
# Try by ID first
resource = list_funct(*parent_args, id=name)[root_tag]
if len(resource) == 1:
return resource[0]
# Try by name next
resource = list_funct(*parent_args, name=name)[root_tag]
if len(resource) == 1:
return resource[0]
if len(resource) > 1:
msg = ("{0} {1} found with name or ID of {2}. Please try "
"again with UUID".format(len(resource), resource_name,
name))
raise exceptions.CommandError(msg)
else:
# Try by name first
resource = list_funct(*parent_args, name=name)[root_tag]
if len(resource) == 1:
return resource[0]
if len(resource) > 1:
msg = ("{0} {1} found with name or ID of {2}. Please try "
"again with UUID".format(len(resource), resource_name,
name))
raise exceptions.CommandError(msg)
# Try by ID next
resource = list_funct(*parent_args, id=name)[root_tag]
if len(resource) == 1:
return resource[0]
# We didn't find what we were looking for, raise a consistent error.
msg = "Unable to locate {0} in {1}".format(name, resource_name)
raise exceptions.CommandError(msg)
def get_resource_id(resource, resource_name, name):
"""Converts a resource name into a UUID for consumption for the API
@ -84,35 +141,22 @@ def get_resource_id(resource, resource_name, name):
name
).id
return project_id
else:
return 'non-uuid'
elif resource_name == 'members':
names = [re for re in resource(name['pool_id'])['members']
if re.get('id') == name['member_id']
or re.get('name') == name['member_id']]
name = name['member_id']
if len(names) > 1:
msg = ("{0} {1} found with name or ID of {2}. Please try "
"again with UUID".format(len(names), resource_name,
name))
raise exceptions.CommandError(msg)
else:
return names[0].get('id')
elif resource_name == 'l7rules':
names = [re for re in resource(name['l7policy_id'])['rules']
if re.get('id') == name['l7rule_id']]
name = name['l7rule_id']
return names[0].get('id')
else:
names = [re for re in resource()[resource_name]
if re.get('name') == name or re.get('id') == name]
if len(names) > 1:
msg = ("{0} {1} found with name or ID of {2}. Please try "
"again with UUID".format(len(names), resource_name,
name))
raise exceptions.CommandError(msg)
else:
return names[0].get('id')
return 'non-uuid'
if resource_name == 'members':
member = _find_resource(resource, resource_name, 'members',
name['member_id'], parent=name['pool_id'])
return member.get('id')
if resource_name == 'l7rules':
l7rule = _find_resource(resource, resource_name, 'rules',
name['l7rule_id'],
parent=name['l7policy_id'])
return l7rule.get('id')
resource = _find_resource(resource, resource_name, resource_name, name)
return resource.get(primary_key)
except IndexError:
msg = "Unable to locate {0} in {1}".format(name, resource_name)
raise exceptions.CommandError(msg)

View File

@ -93,6 +93,7 @@ class TestFlavorDelete(TestFlavor):
verifylist = [
('flavor', 'unknown_flavor')
]
self.api_mock.flavor_list.return_value = {'flavors': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -95,6 +95,8 @@ class TestFlavorProfileDelete(TestFlavorProfile):
verifylist = [
('flavorprofile', 'unknown_flavorprofile')
]
self.api_mock.flavorprofile_list.return_value = {
'flavorprofiles': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -84,6 +84,8 @@ class TestHealthMonitorDelete(TestHealthMonitor):
verifylist = [
('health_monitor', 'unknown_hm')
]
self.api_mock.health_monitor_list.return_value = {
'healthmonitors': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -83,6 +83,7 @@ class TestL7PolicyDelete(TestL7Policy):
verifylist = [
('l7policy', 'unknown_policy')
]
self.api_mock.l7policy_list.return_value = {'l7policies': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -91,6 +91,7 @@ class TestListenerDelete(TestListener):
verifylist = [
('listener', 'unknown_lb')
]
self.api_mock.listener_list.return_value = {'listeners': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -219,6 +219,8 @@ class TestLoadBalancerDelete(TestLoadBalancer):
verifylist = [
('loadbalancer', 'unknown_lb')
]
self.api_mock.load_balancer_list.return_value = {
'loadbalancers': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -82,6 +82,7 @@ class TestPoolDelete(TestPool):
verifylist = [
('pool', 'unknown_pool')
]
self.api_mock.pool_list.return_value = {'pools': []}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Improved the client performance when using a name instead of the
resource ID.