Avoid UnhashableKeyWarning in api.nova.novaclient
In python3, novaclient APIVersion instance is not hashable. If APIVersion insstance is passed to novaclient(), UnhashableKeyWarning will be emitted. An error message on UnhashableKeyWarning is emitted repeatedly and this can pollute error log with unuseful messages. To convert all unhashable arguments into hashable variables, a new decorator memoized_with_argconv is introduced. This decorator takes a converter function. Change-Id: I773355b9332b3b195576b51cc81eda80aa4402ed Closes-Bug: #1790929
This commit is contained in:
parent
78090176ea
commit
d68447452c
|
@ -81,3 +81,36 @@ class MemoizedTests(test.TestCase):
|
|||
self.assertEqual(output2[position], leader)
|
||||
# check that some_other_func returned a memoized list.
|
||||
self.assertIs(output1, output2)
|
||||
|
||||
def test_memoized_with_argcnv(self):
|
||||
value_list = []
|
||||
|
||||
def converter(*args, **kwargs):
|
||||
new_args = tuple(reversed(args))
|
||||
new_kwargs = dict((k, v + 1) for k, v in kwargs.items())
|
||||
return new_args, new_kwargs
|
||||
|
||||
@memoized.memoized_with_argconv(converter)
|
||||
def target_func(*args, **kwargs):
|
||||
value_list.append(1)
|
||||
return args, kwargs
|
||||
|
||||
for i in range(3):
|
||||
ret_args, ret_kwargs = target_func(1, 2, 3)
|
||||
self.assertEqual((3, 2, 1), ret_args)
|
||||
self.assertEqual({}, ret_kwargs)
|
||||
self.assertEqual(1, len(value_list))
|
||||
|
||||
value_list = []
|
||||
for i in range(3):
|
||||
ret_args, ret_kwargs = target_func(a=1, b=2, c=3)
|
||||
self.assertEqual(tuple(), ret_args)
|
||||
self.assertEqual({'a': 2, 'b': 3, 'c': 4}, ret_kwargs)
|
||||
self.assertEqual(1, len(value_list))
|
||||
|
||||
value_list = []
|
||||
for i in range(3):
|
||||
ret_args, ret_kwargs = target_func(1, 2, a=3, b=4)
|
||||
self.assertEqual((2, 1), ret_args)
|
||||
self.assertEqual({'a': 4, 'b': 5}, ret_kwargs)
|
||||
self.assertEqual(1, len(value_list))
|
||||
|
|
|
@ -178,3 +178,31 @@ def memoized_with_request(request_func, request_index=0):
|
|||
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
||||
|
||||
def memoized_with_argconv(convert_func):
|
||||
"""Decorator for caching functions which receive unhashable arguments
|
||||
|
||||
This decorator is a generalized version of memoized_with_request.
|
||||
There are cases where argument(s) other than 'request' are also unhashable.
|
||||
For such cases, such arguments also need to be converted into hashable
|
||||
variables.
|
||||
|
||||
'convert_func' is responsible for replacing unhashable arguments
|
||||
into corresponding hashable variables.
|
||||
|
||||
'convert_func' receives original arguments as its arguments and
|
||||
it must return a full arguments including converted arguments.
|
||||
|
||||
See openstack_dashboard.api.nova as an example.
|
||||
"""
|
||||
def wrapper(func):
|
||||
memoized_func = memoized(func)
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
args, kwargs = convert_func(*args, **kwargs)
|
||||
return memoized_func(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
|
|
@ -36,8 +36,7 @@ from novaclient.v2 import servers as nova_servers
|
|||
|
||||
from horizon import exceptions as horizon_exceptions
|
||||
from horizon.utils import functions as utils
|
||||
from horizon.utils.memoized import memoized
|
||||
from horizon.utils.memoized import memoized_with_request
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api import microversions
|
||||
|
@ -58,7 +57,7 @@ INSECURE = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
|||
CACERT = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||
|
||||
|
||||
@memoized
|
||||
@memoized.memoized
|
||||
def get_microversion(request, features):
|
||||
client = novaclient(request)
|
||||
min_ver, max_ver = api_versions._get_server_version_range(client)
|
||||
|
@ -267,7 +266,14 @@ def get_auth_params_from_request(request):
|
|||
)
|
||||
|
||||
|
||||
@memoized_with_request(get_auth_params_from_request)
|
||||
def _argconv_for_novaclient(request, version=None):
|
||||
req_param = get_auth_params_from_request(request)
|
||||
if isinstance(version, api_versions.APIVersion):
|
||||
version = version.get_string()
|
||||
return (req_param, version), {}
|
||||
|
||||
|
||||
@memoized.memoized_with_argconv(_argconv_for_novaclient)
|
||||
def novaclient(request_auth_params, version=None):
|
||||
(
|
||||
username,
|
||||
|
@ -361,7 +367,7 @@ def flavor_get(request, flavor_id, get_extras=False):
|
|||
|
||||
|
||||
@profiler.trace
|
||||
@memoized
|
||||
@memoized.memoized
|
||||
def flavor_list(request, is_public=True, get_extras=False):
|
||||
"""Get the list of available instance sizes (flavors)."""
|
||||
flavors = novaclient(request).flavors.list(is_public=is_public)
|
||||
|
@ -397,7 +403,7 @@ def update_pagination(entities, page_size, marker, sort_dir, sort_key,
|
|||
|
||||
|
||||
@profiler.trace
|
||||
@memoized
|
||||
@memoized.memoized
|
||||
def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
|
||||
paginate=False, sort_key="name", sort_dir="desc",
|
||||
reversed_order=False):
|
||||
|
@ -427,7 +433,7 @@ def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
|
|||
|
||||
|
||||
@profiler.trace
|
||||
@memoized
|
||||
@memoized.memoized
|
||||
def flavor_access_list(request, flavor=None):
|
||||
"""Get the list of access instance sizes (flavors)."""
|
||||
return novaclient(request).flavor_access.list(flavor=flavor)
|
||||
|
@ -1060,7 +1066,7 @@ def interface_detach(request, server, port_id):
|
|||
|
||||
|
||||
@profiler.trace
|
||||
@memoized_with_request(novaclient)
|
||||
@memoized.memoized_with_request(novaclient)
|
||||
def list_extensions(nova_api):
|
||||
"""List all nova extensions, except the ones in the blacklist."""
|
||||
blacklist = set(getattr(settings,
|
||||
|
@ -1080,7 +1086,7 @@ def _list_extensions_wrap(request):
|
|||
|
||||
|
||||
@profiler.trace
|
||||
@memoized_with_request(_list_extensions_wrap, 1)
|
||||
@memoized.memoized_with_request(_list_extensions_wrap, 1)
|
||||
def extension_supported(extension_name, supported_ext_names):
|
||||
"""Determine if nova supports a given extension name.
|
||||
|
||||
|
|
Loading…
Reference in New Issue