|
|
|
@ -61,6 +61,39 @@ aci_argument_spec = dict(
|
|
|
|
|
validate_certs=dict(type='bool', default=True),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
URL_MAPPING = dict(
|
|
|
|
|
action_rule=dict(aci_class='rtctrlAttrP', mo='attr-', key='name'),
|
|
|
|
|
aep=dict(aci_class='infraAttEntityP', mo='infra/attentp-', key='name'),
|
|
|
|
|
ap=dict(aci_class='fvAp', mo='ap-', key='name'),
|
|
|
|
|
bd=dict(aci_class='fvBD', mo='BD-', key='name'),
|
|
|
|
|
bd_l3out=dict(aci_class='fvRsBDToOut', mo='rsBDToOut-', key='tnL3extOutName'),
|
|
|
|
|
contract=dict(aci_class='vzBrCP', mo='brc-', key='name'),
|
|
|
|
|
entry=dict(aci_class='vzEntry', mo='e-', key='name'),
|
|
|
|
|
epg=dict(aci_class='fvAEPg', mo='epg-', key='name'),
|
|
|
|
|
epg_consumer=dict(aci_class='fvRsCons', mo='rscons-', key='tnVzBrCPName'),
|
|
|
|
|
epg_domain=dict(aci_class='fvRsDomAtt', mo='rsdomAtt-', key='tDn'),
|
|
|
|
|
epg_provider=dict(aci_class='fvRsProv', mo='rsprov-', key='tnVzBrCPName'),
|
|
|
|
|
epr_policy=dict(aci_class='fvEpRetPol', mo='epRPol-', key='name'),
|
|
|
|
|
fc_policy=dict(aci_class='fcIfPol', mo='infra/fcIfPol-', key='name'),
|
|
|
|
|
filter=dict(aci_class='vzFilter', mo='flt-', key='name'),
|
|
|
|
|
gateway_addr=dict(aci_class='fvSubnet', mo='subnet-', key='ip'),
|
|
|
|
|
l2_policy=dict(aci_class='l2IfPol', mo='infra/l2IfP-', key='name'),
|
|
|
|
|
lldp_policy=dict(aci_class='lldpIfPol', mo='infra/lldpIfP-', key='name'),
|
|
|
|
|
mcp=dict(aci_class='mcpIfPol', mo='infra/mcpIfP-', key='name'),
|
|
|
|
|
monitoring_policy=dict(aci_class='monEPGPol', mo='monepg-', key='name'),
|
|
|
|
|
port_channel=dict(aci_class='lacpLagPol', mo='infra/lacplagp-', key='name'),
|
|
|
|
|
port_security=dict(aci_class='l2PortSecurityPol', mo='infra/portsecurityP-', key='name'),
|
|
|
|
|
rtp=dict(aci_class='l3extRouteTagPol', mo='rttag-', key='name'),
|
|
|
|
|
subject=dict(aci_class='vzSubj', mo='subj-', key='name'),
|
|
|
|
|
subject_filter=dict(aci_class='vzRsSubjFiltAtt', mo='rssubjFiltAtt-', key='tnVzFilterName'),
|
|
|
|
|
taboo_contract=dict(aci_class='vzTaboo', mo='taboo-', key='name'),
|
|
|
|
|
tenant=dict(aci_class='fvTenant', mo='tn-', key='name'),
|
|
|
|
|
tenant_span_dst_grp=dict(aci_class='spanDestGrp', mo='destgrp-', key='name'),
|
|
|
|
|
tenant_span_src_grp=dict(aci_class='spanSrcGrp', mo='srcgrp-', key='name'),
|
|
|
|
|
tenant_span_src_grp_dst_grp=dict(aci_class='spanSpanLbl', mo='spanlbl-', key='name'),
|
|
|
|
|
vrf=dict(aci_class='fvCtx', mo='ctx-', key='name'),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def aci_response_error(result):
|
|
|
|
|
''' Set error information when found '''
|
|
|
|
@ -243,6 +276,231 @@ class ACIModule(object):
|
|
|
|
|
if self.result['diff']['before'] != self.result['diff']['after']:
|
|
|
|
|
self.result['changed'] = True
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
:param root_class: Type str.
|
|
|
|
|
The top-level class naming parameter per the modules (EX: tenant).
|
|
|
|
|
:param sublass_1: Type str.
|
|
|
|
|
The second-level class naming parameter per the modules (EX: bd).
|
|
|
|
|
:param sublass_2: Type str.
|
|
|
|
|
The third-level class naming parameter per the modules (EX: gateway).
|
|
|
|
|
:param sublass_3: Type str.
|
|
|
|
|
The fourth-level class naming parameter per the modules.
|
|
|
|
|
:param child_classes: Type tuple.
|
|
|
|
|
The list of child classes that the module supports along with the object.
|
|
|
|
|
:return: The path and filter_string needed to build the full URL.
|
|
|
|
|
"""
|
|
|
|
|
if child_classes is None:
|
|
|
|
|
child_includes = ''
|
|
|
|
|
else:
|
|
|
|
|
child_includes = ','.join(child_classes)
|
|
|
|
|
child_includes = '&rsp-subtree=full&rsp-subtree-class=' + child_includes
|
|
|
|
|
|
|
|
|
|
if subclass_3 is not None:
|
|
|
|
|
path, filter_string = self._construct_url_4(root_class, subclass_1, subclass_2, subclass_3, child_includes)
|
|
|
|
|
elif subclass_2 is not None:
|
|
|
|
|
path, filter_string = self._construct_url_3(root_class, subclass_1, subclass_2, child_includes)
|
|
|
|
|
elif subclass_1 is not None:
|
|
|
|
|
path, filter_string = self._construct_url_2(root_class, subclass_1, child_includes)
|
|
|
|
|
else:
|
|
|
|
|
path, filter_string = self._construct_url_1(root_class, child_includes)
|
|
|
|
|
|
|
|
|
|
self.result['url'] = '{}://{}/{}'.format(self.module.params['protocol'], self.module.params['hostname'], path)
|
|
|
|
|
self.result['filter_string'] = filter_string
|
|
|
|
|
|
|
|
|
|
def _construct_url_1(self, obj_class, child_includes):
|
|
|
|
|
"""
|
|
|
|
|
This method is used by get_url when the object is the top-level class.
|
|
|
|
|
"""
|
|
|
|
|
obj = self.module.params.get(obj_class)
|
|
|
|
|
obj_dict = URL_MAPPING[obj_class]
|
|
|
|
|
obj_class = obj_dict['aci_class']
|
|
|
|
|
obj_mo = obj_dict['mo']
|
|
|
|
|
|
|
|
|
|
# State is present or absent
|
|
|
|
|
if self.module.params['state'] != 'query':
|
|
|
|
|
path = 'api/mo/uni/{}[{}].json'.format(obj_mo, obj)
|
|
|
|
|
filter_string = '?rsp-prop-include=config-only' + child_includes
|
|
|
|
|
# Query for all objects of the module's class
|
|
|
|
|
elif obj is None:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
# Query for a specific object in the module's class
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/mo/uni/{}[{}].json'.format(obj_mo, obj)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
|
|
|
|
|
# Append child_includes to filter_string if filter string is empty
|
|
|
|
|
if child_includes is not None and filter_string == '':
|
|
|
|
|
filter_string = child_includes.replace('&', '?', 1)
|
|
|
|
|
|
|
|
|
|
return path, filter_string
|
|
|
|
|
|
|
|
|
|
def _construct_url_2(self, parent_class, obj_class, child_includes):
|
|
|
|
|
"""
|
|
|
|
|
This method is used by get_url when the object is the second-level class.
|
|
|
|
|
"""
|
|
|
|
|
parent = self.module.params.get(parent_class)
|
|
|
|
|
parent_dict = URL_MAPPING[parent_class]
|
|
|
|
|
parent_class = parent_dict['aci_class']
|
|
|
|
|
parent_mo = parent_dict['mo']
|
|
|
|
|
obj = self.module.params.get(obj_class)
|
|
|
|
|
obj_dict = URL_MAPPING[obj_class]
|
|
|
|
|
obj_class = obj_dict['aci_class']
|
|
|
|
|
obj_mo = obj_dict['mo']
|
|
|
|
|
obj_key = obj_dict['key']
|
|
|
|
|
|
|
|
|
|
if not child_includes:
|
|
|
|
|
self_child_includes = '?rsp-subtree=full&rsp-subtree-class=' + obj_class
|
|
|
|
|
else:
|
|
|
|
|
self_child_includes = child_includes.replace('&', '?', 1) + ',' + obj_class
|
|
|
|
|
|
|
|
|
|
# State is present or absent
|
|
|
|
|
if self.module.params['state'] != 'query':
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}].json'.format(parent_mo, parent, obj_mo, obj)
|
|
|
|
|
filter_string = '?rsp-prop-include=config-only' + child_includes
|
|
|
|
|
# Query for all objects of the module's class
|
|
|
|
|
elif obj is None and parent is None:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
# Queries when parent object is provided
|
|
|
|
|
elif parent is not None:
|
|
|
|
|
# Query for specific object in the module's class
|
|
|
|
|
if obj is not None:
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}].json'.format(parent_mo, parent, obj_mo, obj)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
# Query for all object's of the module's class that belong to a specific parent object
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/mo/uni/{}[{}].json'.format(parent_mo, parent)
|
|
|
|
|
filter_string = self_child_includes
|
|
|
|
|
# Query for all objects of the module's class that match the provided ID value
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = '?query-target-filter=eq({}.{}, \"{}\")'.format(obj_class, obj_key, obj) + child_includes
|
|
|
|
|
|
|
|
|
|
# Append child_includes to filter_string if filter string is empty
|
|
|
|
|
if child_includes is not None and filter_string == '':
|
|
|
|
|
filter_string = child_includes.replace('&', '?', 1)
|
|
|
|
|
|
|
|
|
|
return path, filter_string
|
|
|
|
|
|
|
|
|
|
def _construct_url_3(self, root_class, parent_class, obj_class, child_includes):
|
|
|
|
|
"""
|
|
|
|
|
This method is used by get_url when the object is the third-level class.
|
|
|
|
|
"""
|
|
|
|
|
root = self.module.params.get(root_class)
|
|
|
|
|
root_dict = URL_MAPPING[root_class]
|
|
|
|
|
root_class = root_dict['aci_class']
|
|
|
|
|
root_mo = root_dict['mo']
|
|
|
|
|
parent = self.module.params.get(parent_class)
|
|
|
|
|
parent_dict = URL_MAPPING[parent_class]
|
|
|
|
|
parent_class = parent_dict['aci_class']
|
|
|
|
|
parent_mo = parent_dict['mo']
|
|
|
|
|
parent_key = parent_dict['key']
|
|
|
|
|
obj = self.module.params.get(obj_class)
|
|
|
|
|
obj_dict = URL_MAPPING[obj_class]
|
|
|
|
|
obj_class = obj_dict['aci_class']
|
|
|
|
|
obj_mo = obj_dict['mo']
|
|
|
|
|
obj_key = obj_dict['key']
|
|
|
|
|
|
|
|
|
|
if not child_includes:
|
|
|
|
|
self_child_includes = '&rsp-subtree=full&rsp-subtree-class=' + obj_class
|
|
|
|
|
else:
|
|
|
|
|
self_child_includes = '{},{}'.format(child_includes, obj_class)
|
|
|
|
|
|
|
|
|
|
if not child_includes:
|
|
|
|
|
parent_self_child_includes = '&rsp-subtree=full&rsp-subtree-class={},{}'.format(parent_class, obj_class)
|
|
|
|
|
else:
|
|
|
|
|
parent_self_child_includes = '{},{},{}'.format(child_includes, parent_class, obj_class)
|
|
|
|
|
|
|
|
|
|
# State is ablsent or present
|
|
|
|
|
if self.module.params['state'] != 'query':
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}]/{}[{}].json'.format(root_mo, root, parent_mo, parent, obj_mo, obj)
|
|
|
|
|
filter_string = '?rsp-prop-include=config-only' + child_includes
|
|
|
|
|
# Query for all objects of the module's class
|
|
|
|
|
elif obj is None and parent is None and root is None:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
# Queries when root object is provided
|
|
|
|
|
elif root is not None:
|
|
|
|
|
# Queries when parent object is provided
|
|
|
|
|
if parent is not None:
|
|
|
|
|
# Query for a specific object of the module's class
|
|
|
|
|
if obj is not None:
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}]/{}[{}].json'.format(root_mo, root, parent_mo, parent, obj_mo, obj)
|
|
|
|
|
filter_string = ''
|
|
|
|
|
# Query for all objects of the module's class that belong to a specific parent object
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}].json'.format(root_mo, root, parent_mo, parent)
|
|
|
|
|
filter_string = self_child_includes.replace('&', '?', 1)
|
|
|
|
|
# Query for all objects of the module's class that match the provided ID value and belong to a specefic root object
|
|
|
|
|
elif obj is not None:
|
|
|
|
|
path = 'api/mo/uni/{}[{}].json'.format(root_mo, root)
|
|
|
|
|
filter_string = '?rsp-subtree-filter=eq({}.{}, \"{}\"){}'.format(obj_class, obj_key, obj, self_child_includes)
|
|
|
|
|
# Query for all objects of the module's class that belong to a specific root object
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/mo/uni/{}[{}].json'.format(root_mo, root)
|
|
|
|
|
filter_string = '?' + parent_self_child_includes
|
|
|
|
|
# Queries when parent object is provided but root object is not provided
|
|
|
|
|
elif parent is not None:
|
|
|
|
|
# Query for all objects of the module's class that belong to any parent class
|
|
|
|
|
# matching the provided ID values for both object and parent object
|
|
|
|
|
if obj is not None:
|
|
|
|
|
path = 'api/class/{}.json'.format(parent_class)
|
|
|
|
|
filter_string = '?query-target-filter=eq({}.{}, \"{}\"){}&rsp-subtree-filter=eq({}.{}, \"{}\")'.format(
|
|
|
|
|
parent_class, parent_key, parent, self_child_includes, obj_class, obj_key, obj)
|
|
|
|
|
# Query for all objects of the module's class that belong to any parent class
|
|
|
|
|
# matching the provided ID value for the parent object
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/class/{}.json'.format(parent_class)
|
|
|
|
|
filter_string = '?query-target-filter=eq({}.{}, \"{}\"){}'.format(parent_class, parent_key, parent, self_child_includes)
|
|
|
|
|
# Query for all objects of the module's class matching the provided ID value of the object
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = '?query-target-filter=eq({}.{}, \"{}\")'.format(obj_class, obj_key, obj) + child_includes
|
|
|
|
|
|
|
|
|
|
# append child_includes to filter_string if filter string is empty
|
|
|
|
|
if child_includes is not None and filter_string == '':
|
|
|
|
|
filter_string = child_includes.replace('&', '?', 1)
|
|
|
|
|
|
|
|
|
|
return path, filter_string
|
|
|
|
|
|
|
|
|
|
def _construct_url_4(self, root_class, sec_class, parent_class, obj_class, child_includes):
|
|
|
|
|
"""
|
|
|
|
|
This method is used by get_url when the object is the third-level class.
|
|
|
|
|
"""
|
|
|
|
|
root = self.module.params.get(root_class)
|
|
|
|
|
root_dict = URL_MAPPING[root_class]
|
|
|
|
|
root_class = root_dict['aci_class']
|
|
|
|
|
root_mo = root_dict['mo']
|
|
|
|
|
sec = self.module.params.get(sec_class)
|
|
|
|
|
sec_dict = URL_MAPPING[sec_class]
|
|
|
|
|
sec_class = sec_dict['aci_class']
|
|
|
|
|
sec_mo = sec_dict['mo']
|
|
|
|
|
# sec_key = sec_dict['key']
|
|
|
|
|
parent = self.module.params.get(parent_class)
|
|
|
|
|
parent_dict = URL_MAPPING[parent_class]
|
|
|
|
|
parent_class = parent_dict['aci_class']
|
|
|
|
|
parent_mo = parent_dict['mo']
|
|
|
|
|
# parent_key = parent_dict['key']
|
|
|
|
|
obj = self.module.params.get(obj_class)
|
|
|
|
|
obj_dict = URL_MAPPING[obj_class]
|
|
|
|
|
obj_class = obj_dict['aci_class']
|
|
|
|
|
obj_mo = obj_dict['mo']
|
|
|
|
|
# obj_key = obj_dict['key']
|
|
|
|
|
|
|
|
|
|
# State is ablsent or present
|
|
|
|
|
if self.module.params['state'] != 'query':
|
|
|
|
|
path = 'api/mo/uni/{}[{}]/{}[{}]/{}[{}]/{}[{}].json'.format(root_mo, root, sec_mo, sec, parent_mo, parent, obj_mo, obj)
|
|
|
|
|
filter_string = '?rsp-prop-include=config-only' + child_includes
|
|
|
|
|
else:
|
|
|
|
|
path = 'api/class/{}.json'.format(obj_class)
|
|
|
|
|
filter_string = child_includes
|
|
|
|
|
|
|
|
|
|
return path, filter_string
|
|
|
|
|
|
|
|
|
|
def delete_config(self):
|
|
|
|
|
"""
|
|
|
|
|
This method is used to handle the logic when the modules state is equal to absent. The method only pushes a change if
|
|
|
|
@ -375,18 +633,16 @@ class ACIModule(object):
|
|
|
|
|
|
|
|
|
|
return child_updates
|
|
|
|
|
|
|
|
|
|
def get_existing(self, filter_string=""):
|
|
|
|
|
def get_existing(self):
|
|
|
|
|
"""
|
|
|
|
|
This method is used to get the existing object(s) based on the path specified in the module. Each module should
|
|
|
|
|
build the URL so that if the object's name is supplied, then it will retrieve the configuration for that particular
|
|
|
|
|
object, but if no name is supplied, then it will retrieve all MOs for the class. Following this method will ensure
|
|
|
|
|
that this method can be used to supply the existing configuration when using the get_diff method. The response, status,
|
|
|
|
|
and existing configuration will be added to the self.result dictionary.
|
|
|
|
|
|
|
|
|
|
:param filter_string: Type str.
|
|
|
|
|
The filter to use in order to retrieve the filtered configuration.
|
|
|
|
|
"""
|
|
|
|
|
uri = self.result['url'] + filter_string
|
|
|
|
|
uri = self.result['url'] + self.result['filter_string']
|
|
|
|
|
|
|
|
|
|
resp, info = fetch_url(self.module, uri,
|
|
|
|
|
headers=self.headers,
|
|
|
|
|
method='GET',
|
|
|
|
|