construct_deep_url() (#53475)
* Updates aci.py with the ability to add ACI objects to any depth Changes start at line 411 (construct_deep_url() and supporting functions). One minor change to line 633 (the original construct_url()) to provide for testability: ...join(sorted(self.child_classes)) vs ...join(self.child_classes) I am also attaching two test files. One characterizing the existing construct_url() and the matching test set for construct_deep_url() to support my efforts and proof of parity * Two PyTest files to support construct_deep_url These two files provide testing parity, one characterizing the original construct_url() function and the other proofing construct_deep_url(). The ...deep_url.py test file goes five layers deep to provide better validation for the function * Correcting previous upload to incorrect folder These two files provide testing parity, one characterizing the original construct_url() function and the other proofing construct_deep_url(). The ...deep_url.py test file goes five layers deep to provide better validation for the function * Deleting for file name change per Matt Clay * Deleting for file name change per Matt Clay * Correcting file names per Matt Clay @mattclay Thanks again for your continued guidance and patience. Please cancel the previous (incorrect) request * Wrong location for test file * Wrong location for test file * First attempt to comply with suggestions lib/ansible/module_utils/network/aci/aci.py:517:0: SyntaxWarning: "is not" with a literal. Did you mean "!="? lib/ansible/module_utils/network/aci/aci.py:534:0: SyntaxWarning: "is not" with a literal. Did you mean "!="? lib/ansible/module_utils/network/aci/aci.py:558:161: E501 line too long (210 > 160 characters) * First attempt to comply with suggestions test/units/module_utils/network/aci/test_aci_construct_url.py:1:14: SyntaxError: import pytest test/units/module_utils/network/aci/test_aci_deep_url.py:1:14: SyntaxError: import pytest test/units/module_utils/network/aci/test_aci_construct_url.py:0:0: use "\n" for line endings instead of "\r\n" test/units/module_utils/network/aci/test_aci_deep_url.py:0:0: use "\n" for line endings instead of "\r\n" Shortened test function names (less descriptive) * Second attempt to comply with suggestions * Second attempt to comply with suggestions * Third attempt to comply with suggestions * Third attempt to comply with suggestions * Pro Tip: Convert from 'CRLF' to 'LF' in VSCode It's on the status bar to the right * Added setup() support for tests * Continued corrections to support testing * Added two mocks to support testing I could not find where to place fakes/mocks, so please let me know if the current location is incorrect * Adding tmpdir property to mock_basic.py * Added last blank line to mock_basic.py To pass sanity test * Attempt to correct setup() issues * Attempt to correct setup() issues * Attempt to correct setup() issues * Attempt to correct setup() issues * Withdrawing pending injectability tweak to aci.py * Withdrawing pending injectability tweak to aci.py * Withdrawing pending injectability tweak to aci.py * Withdrawing pending injectability tweak to aci.py
This commit is contained in:
parent
391a1042c2
commit
bb50fc3889
1 changed files with 185 additions and 1 deletions
|
@ -10,6 +10,7 @@
|
||||||
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
|
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
|
||||||
# Copyright: (c) 2017, Jacob McGill (@jmcgill298)
|
# Copyright: (c) 2017, Jacob McGill (@jmcgill298)
|
||||||
# Copyright: (c) 2017, Swetha Chunduri (@schunduri)
|
# Copyright: (c) 2017, Swetha Chunduri (@schunduri)
|
||||||
|
# Copyright: (c) 2019, Rob Huelga (@RobW3LGA)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
@ -412,6 +413,189 @@ class ACIModule(object):
|
||||||
elif len(accepted_params) > 1:
|
elif len(accepted_params) > 1:
|
||||||
return 'and(' + ','.join(['eq({0}.{1}, "{2}")'.format(obj_class, k, v) for (k, v) in accepted_params.items()]) + ')'
|
return 'and(' + ','.join(['eq({0}.{1}, "{2}")'.format(obj_class, k, v) for (k, v) in accepted_params.items()]) + ')'
|
||||||
|
|
||||||
|
def _deep_url_path_builder(self, obj):
|
||||||
|
target_class = obj['target_class']
|
||||||
|
target_filter = obj['target_filter']
|
||||||
|
subtree_class = obj['subtree_class']
|
||||||
|
subtree_filter = obj['subtree_filter']
|
||||||
|
object_rn = obj['object_rn']
|
||||||
|
mo = obj['module_object']
|
||||||
|
add_subtree_filter = obj['add_subtree_filter']
|
||||||
|
add_target_filter = obj['add_target_filter']
|
||||||
|
|
||||||
|
if self.module.params['state'] in ('absent', 'present') and mo is not None:
|
||||||
|
self.path = 'api/mo/uni/{0}.json'.format(object_rn)
|
||||||
|
self.update_qs({'rsp-prop-include': 'config-only'})
|
||||||
|
|
||||||
|
else:
|
||||||
|
# State is 'query'
|
||||||
|
if object_rn is not None:
|
||||||
|
# Query for a specific object in the module's class
|
||||||
|
self.path = 'api/mo/uni/{0}.json'.format(object_rn)
|
||||||
|
else:
|
||||||
|
self.path = 'api/class/{0}.json'.format(target_class)
|
||||||
|
|
||||||
|
if add_target_filter:
|
||||||
|
self.update_qs(
|
||||||
|
{'query-target-filter': self.build_filter(target_class, target_filter)})
|
||||||
|
|
||||||
|
if add_subtree_filter:
|
||||||
|
self.update_qs(
|
||||||
|
{'rsp-subtree-filter': self.build_filter(subtree_class, subtree_filter)})
|
||||||
|
|
||||||
|
if 'port' in self.params and self.params['port'] is not None:
|
||||||
|
self.url = '{protocol}://{host}:{port}/{path}'.format(
|
||||||
|
path=self.path, **self.module.params)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.url = '{protocol}://{host}/{path}'.format(
|
||||||
|
path=self.path, **self.module.params)
|
||||||
|
|
||||||
|
if self.child_classes:
|
||||||
|
self.update_qs(
|
||||||
|
{'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(sorted(self.child_classes))})
|
||||||
|
|
||||||
|
def _deep_url_parent_object(self, parent_objects, parent_class):
|
||||||
|
|
||||||
|
for parent_object in parent_objects:
|
||||||
|
if parent_object['aci_class'] is parent_class:
|
||||||
|
return parent_object
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def construct_deep_url(self, target_object, parent_objects=None, child_classes=None):
|
||||||
|
"""
|
||||||
|
This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC.
|
||||||
|
|
||||||
|
:param target_object: The target class dictionary containing parent_class, aci_class, aci_rn, target_filter, and module_object keys.
|
||||||
|
:param parent_objects: The parent class list of dictionaries containing parent_class, aci_class, aci_rn, target_filter, and module_object keys.
|
||||||
|
:param child_classes: The list of child classes that the module supports along with the object.
|
||||||
|
:type target_object: dict
|
||||||
|
:type parent_objects: list[dict]
|
||||||
|
:type child_classes: list[string]
|
||||||
|
:return: The path and filter_string needed to build the full URL.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.filter_string = ''
|
||||||
|
rn_builder = None
|
||||||
|
subtree_classes = None
|
||||||
|
add_subtree_filter = False
|
||||||
|
add_target_filter = False
|
||||||
|
has_target_query = False
|
||||||
|
has_target_query_compare = False
|
||||||
|
has_target_query_difference = False
|
||||||
|
has_target_query_called = False
|
||||||
|
|
||||||
|
if child_classes is None:
|
||||||
|
self.child_classes = set()
|
||||||
|
else:
|
||||||
|
self.child_classes = set(child_classes)
|
||||||
|
|
||||||
|
target_parent_class = target_object['parent_class']
|
||||||
|
target_class = target_object['aci_class']
|
||||||
|
target_rn = target_object['aci_rn']
|
||||||
|
target_filter = target_object['target_filter']
|
||||||
|
target_module_object = target_object['module_object']
|
||||||
|
|
||||||
|
url_path_object = dict(
|
||||||
|
target_class=target_class,
|
||||||
|
target_filter=target_filter,
|
||||||
|
subtree_class=target_class,
|
||||||
|
subtree_filter=target_filter,
|
||||||
|
module_object=target_module_object
|
||||||
|
)
|
||||||
|
|
||||||
|
if target_module_object is not None:
|
||||||
|
rn_builder = target_rn
|
||||||
|
else:
|
||||||
|
has_target_query = True
|
||||||
|
has_target_query_compare = True
|
||||||
|
|
||||||
|
if parent_objects is not None:
|
||||||
|
current_parent_class = target_parent_class
|
||||||
|
has_parent_query_compare = False
|
||||||
|
has_parent_query_difference = False
|
||||||
|
is_first_parent = True
|
||||||
|
is_single_parent = None
|
||||||
|
search_classes = set()
|
||||||
|
|
||||||
|
while current_parent_class != 'uni':
|
||||||
|
parent_object = self._deep_url_parent_object(
|
||||||
|
parent_objects=parent_objects, parent_class=current_parent_class)
|
||||||
|
|
||||||
|
if parent_object is not None:
|
||||||
|
parent_parent_class = parent_object['parent_class']
|
||||||
|
parent_class = parent_object['aci_class']
|
||||||
|
parent_rn = parent_object['aci_rn']
|
||||||
|
parent_filter = parent_object['target_filter']
|
||||||
|
parent_module_object = parent_object['module_object']
|
||||||
|
|
||||||
|
if is_first_parent:
|
||||||
|
is_single_parent = True
|
||||||
|
else:
|
||||||
|
is_single_parent = False
|
||||||
|
is_first_parent = False
|
||||||
|
|
||||||
|
if parent_parent_class != 'uni':
|
||||||
|
search_classes.add(parent_class)
|
||||||
|
|
||||||
|
if parent_module_object is not None:
|
||||||
|
if rn_builder is not None:
|
||||||
|
rn_builder = '{0}/{1}'.format(parent_rn,
|
||||||
|
rn_builder)
|
||||||
|
else:
|
||||||
|
rn_builder = parent_rn
|
||||||
|
|
||||||
|
url_path_object['target_class'] = parent_class
|
||||||
|
url_path_object['target_filter'] = parent_filter
|
||||||
|
|
||||||
|
has_target_query = False
|
||||||
|
else:
|
||||||
|
rn_builder = None
|
||||||
|
subtree_classes = search_classes
|
||||||
|
|
||||||
|
has_target_query = True
|
||||||
|
if is_single_parent:
|
||||||
|
has_parent_query_compare = True
|
||||||
|
|
||||||
|
current_parent_class = parent_parent_class
|
||||||
|
else:
|
||||||
|
raise ValueError("Reference error for parent_class '{0}'. Each parent_class must reference a valid object".format(current_parent_class))
|
||||||
|
|
||||||
|
if not has_target_query_difference and not has_target_query_called:
|
||||||
|
if has_target_query is not has_target_query_compare:
|
||||||
|
has_target_query_difference = True
|
||||||
|
else:
|
||||||
|
if not has_parent_query_difference and has_target_query is not has_parent_query_compare:
|
||||||
|
has_parent_query_difference = True
|
||||||
|
has_target_query_called = True
|
||||||
|
|
||||||
|
if not has_parent_query_difference and has_parent_query_compare and target_module_object is not None:
|
||||||
|
add_target_filter = True
|
||||||
|
|
||||||
|
elif has_parent_query_difference and target_module_object is not None:
|
||||||
|
add_subtree_filter = True
|
||||||
|
self.child_classes.add(target_class)
|
||||||
|
|
||||||
|
if has_target_query:
|
||||||
|
add_target_filter = True
|
||||||
|
|
||||||
|
elif has_parent_query_difference and not has_target_query and target_module_object is None:
|
||||||
|
self.child_classes.add(target_class)
|
||||||
|
self.child_classes.update(subtree_classes)
|
||||||
|
|
||||||
|
elif not has_parent_query_difference and not has_target_query and target_module_object is None:
|
||||||
|
self.child_classes.add(target_class)
|
||||||
|
|
||||||
|
elif not has_target_query and is_single_parent and target_module_object is None:
|
||||||
|
self.child_classes.add(target_class)
|
||||||
|
|
||||||
|
url_path_object['object_rn'] = rn_builder
|
||||||
|
url_path_object['add_subtree_filter'] = add_subtree_filter
|
||||||
|
url_path_object['add_target_filter'] = add_target_filter
|
||||||
|
|
||||||
|
self._deep_url_path_builder(url_path_object)
|
||||||
|
|
||||||
def construct_url(self, root_class, subclass_1=None, subclass_2=None, subclass_3=None, child_classes=None):
|
def construct_url(self, root_class, subclass_1=None, subclass_2=None, subclass_3=None, child_classes=None):
|
||||||
"""
|
"""
|
||||||
This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC.
|
This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC.
|
||||||
|
@ -451,7 +635,7 @@ class ACIModule(object):
|
||||||
|
|
||||||
if self.child_classes:
|
if self.child_classes:
|
||||||
# Append child_classes to filter_string if filter string is empty
|
# Append child_classes to filter_string if filter string is empty
|
||||||
self.update_qs({'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(self.child_classes)})
|
self.update_qs({'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(sorted(self.child_classes))})
|
||||||
|
|
||||||
def _construct_url_1(self, obj):
|
def _construct_url_1(self, obj):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue