refactor moudule utils of hwc_utils.py (#55858)

* use navigate_value instead navigate_hash

* add async wait method

* update dict compare

* remove unuse methods

* not all modules have timeouts parameter

* navigate_value, the input data may be None
This commit is contained in:
zengchen 2019-05-09 21:04:52 +08:00 committed by jctanner
parent 34a8594c91
commit d8314e1a45
6 changed files with 305 additions and 327 deletions

View file

@ -3,6 +3,7 @@
# https://opensource.org/licenses/BSD-2-Clause)
import re
import time
import traceback
THIRD_LIBRARIES_IMP_ERR = None
@ -20,59 +21,14 @@ from ansible.module_utils.basic import (AnsibleModule, env_fallback,
from ansible.module_utils._text import to_text
def navigate_hash(source, path, default=None):
if not (source and path):
return None
class HwcModuleException(Exception):
def __init__(self, message):
super(HwcModuleException, self).__init__()
key = path[0]
path = path[1:]
if key not in source:
return default
result = source[key]
if path:
return navigate_hash(result, path, default)
else:
return result
self._message = message
def remove_empty_from_dict(obj):
return _DictClean(
obj,
lambda v: v is not None and v != {} and v != []
)()
def remove_nones_from_dict(obj):
return _DictClean(obj, lambda v: v is not None)()
def replace_resource_dict(item, value):
""" Handles the replacement of dicts with values ->
the needed value for HWC API"""
if isinstance(item, list):
items = []
for i in item:
items.append(replace_resource_dict(i, value))
return items
else:
if not item:
return item
return item.get(value)
def are_dicts_different(expect, actual):
"""Remove all output-only from actual."""
actual_vals = {}
for k, v in actual.items():
if k in expect:
actual_vals[k] = v
expect_vals = {}
for k, v in expect.items():
if k in actual:
expect_vals[k] = v
return DictComparison(expect_vals) != DictComparison(actual_vals)
def __str__(self):
return "[HwcClientException] message=%s" % self._message
class HwcClientException(Exception):
@ -116,9 +72,9 @@ def session_method_wrapper(f):
code = r.status_code
if code not in [200, 201, 202, 203, 204, 205, 206, 207, 208, 226]:
msg = ""
for i in [['message'], ['error', 'message']]:
for i in ['message', 'error.message']:
try:
msg = navigate_hash(result, i)
msg = navigate_value(result, i)
break
except Exception:
pass
@ -281,14 +237,9 @@ class HwcModule(AnsibleModule):
fallback=(env_fallback, ['ANSIBLE_HWC_PROJECT']),
),
region=dict(
required=True, type='str',
type='str',
fallback=(env_fallback, ['ANSIBLE_HWC_REGION']),
),
timeouts=dict(type='dict', options=dict(
create=dict(default='10m', type='str'),
update=dict(default='10m', type='str'),
delete=dict(default='10m', type='str'),
), default={}),
id=dict(type='str')
)
)
@ -296,12 +247,13 @@ class HwcModule(AnsibleModule):
super(HwcModule, self).__init__(*args, **kwargs)
class DictComparison(object):
class _DictComparison(object):
''' This class takes in two dictionaries `a` and `b`.
These are dictionaries of arbitrary depth, but made up of standard
Python types only.
This differ will compare all values in `a` to those in `b`.
Note: Only keys in `a` will be compared. Extra keys in `b` will be ignored.
If value in `a` is None, always returns True, indicating
this value is no need to compare.
Note: On all lists, order does matter.
'''
@ -315,76 +267,136 @@ class DictComparison(object):
return not self.__eq__(other)
def _compare_dicts(self, dict1, dict2):
if len(dict1.keys()) != len(dict2.keys()):
if dict1 is None:
return True
if set(dict1.keys()) != set(dict2.keys()):
return False
return all([
self._compare_value(dict1.get(k), dict2.get(k)) for k in dict1
])
for k in dict1:
if not self._compare_value(dict1.get(k), dict2.get(k)):
return False
return True
def _compare_lists(self, list1, list2):
"""Takes in two lists and compares them."""
if list1 is None:
return True
if len(list1) != len(list2):
return False
difference = []
for index in range(len(list1)):
value1 = list1[index]
if index < len(list2):
value2 = list2[index]
difference.append(self._compare_value(value1, value2))
for i in range(len(list1)):
if not self._compare_value(list1[i], list2[i]):
return False
return all(difference)
return True
def _compare_value(self, value1, value2):
"""
return: True: value1 is same as value2, otherwise False.
"""
if value1 is None:
return True
if not (value1 and value2):
return (not value1) and (not value2)
# Can assume non-None types at this point.
if isinstance(value1, list):
if isinstance(value1, list) and isinstance(value2, list):
return self._compare_lists(value1, value2)
elif isinstance(value1, dict):
elif isinstance(value1, dict) and isinstance(value2, dict):
return self._compare_dicts(value1, value2)
# Always use to_text values to avoid unicode issues.
return (to_text(value1, errors='surrogate_or_strict') == to_text(
value2, errors='surrogate_or_strict'))
def wait_to_finish(target, pending, refresh, timeout, min_interval=1, delay=3):
is_last_time = False
not_found_times = 0
wait = 0
time.sleep(delay)
end = time.time() + timeout
while not is_last_time:
if time.time() > end:
is_last_time = True
obj, status = refresh()
if obj is None:
not_found_times += 1
if not_found_times > 10:
raise HwcModuleException(
"not found the object for %d times" % not_found_times)
else:
return (to_text(value1, errors='surrogate_or_strict')
== to_text(value2, errors='surrogate_or_strict'))
not_found_times = 0
if status in target:
return obj
if pending and status not in pending:
raise HwcModuleException(
"unexpect status(%s) occured" % status)
if not is_last_time:
wait *= 2
if wait < min_interval:
wait = min_interval
elif wait > 10:
wait = 10
time.sleep(wait)
raise HwcModuleException("asycn wait timeout after %d seconds" % timeout)
class _DictClean(object):
def __init__(self, obj, func):
self.obj = obj
self.keep_it = func
def navigate_value(data, index, array_index=None):
if array_index and (not isinstance(array_index, dict)):
raise HwcModuleException("array_index must be dict")
def __call__(self):
return self._clean_dict(self.obj)
d = data
for n in range(len(index)):
if d is None:
return None
def _clean_dict(self, obj):
r = {}
for k, v in obj.items():
v1 = v
if isinstance(v, dict):
v1 = self._clean_dict(v)
elif isinstance(v, list):
v1 = self._clean_list(v)
if self.keep_it(v1):
r[k] = v1
return r
if not isinstance(d, dict):
raise HwcModuleException(
"can't navigate value from a non-dict object")
def _clean_list(self, obj):
r = []
for v in obj:
v1 = v
if isinstance(v, dict):
v1 = self._clean_dict(v)
elif isinstance(v, list):
v1 = self._clean_list(v)
if self.keep_it(v1):
r.append(v1)
return r
i = index[n]
if i not in d:
raise HwcModuleException(
"navigate value failed: key(%s) is not exist in dict" % i)
d = d[i]
if not array_index:
continue
k = ".".join(index[: (n + 1)])
if k not in array_index:
continue
if d is None:
return None
if not isinstance(d, list):
raise HwcModuleException(
"can't navigate value from a non-list object")
j = array_index.get(k)
if j >= len(d):
raise HwcModuleException(
"navigate value failed: the index is out of list")
d = d[j]
return d
def build_path(module, path, kv=None):
@ -411,4 +423,12 @@ def get_region(module):
if module.params['region']:
return module.params['region']
return module.params['project_name'].split("_")[0]
return module.params['project'].split("_")[0]
def is_empty_value(v):
return (not v)
def are_different_dicts(dict1, dict2):
return _DictComparison(dict1) != _DictComparison(dict2)