This causes the line: aci.get_diff(aci_class='fvAEPg') from the uEPG module (listed below) to cause inconsistent results.
Error seen in custom uEPG module based on native EPG module.
Problem appears in the first recurrence of the execution, in the case of delete uEPG using the following playbook to remove inherited EPGs from uEPG work from the second element of the ROLELIST (for every uEPG the first remove will fail and rest will be proceeded as expected):
uEPG:
host: 192.168.1.1
username: "admin"
password: "pass"
validate_certs: no
tenant: "TNT_TNT"
ap: "APP_ANP"
uepg: "{{ item.0.HOSTNAME }}"
bd: "BD_BD"
inherited_epg: "{{ item.1 }}"
state: absent
output_level: debug
register: query_result
with_subelements:
- "{{ list_uEPG.host_list }}"
- ROLELIST
ignore_errors: yes
The playbook, module and data used:
ansible_aci_send.yaml - the playbook
---
- name: test of using uEPG module
gather_facts: no
hosts: localhost
connection: local
tasks:
- name: include vars for templating
include_vars:
file: host_data_created.json
name: list_uEPG
## add section
- name: add EPGs from role list
aci_epg:
host: 192.168.1.1
username: "admin"
password: "pass"
validate_certs: no
name: "{{ item.1 }}"
tenant: "TNT_TNT"
ap: "APP_ANP"
bd: "BD_BD"
state: present
output_level: debug
with_subelements:
- "{{ list_uEPG.host_list }}"
- ROLELIST
ignore_errors: yes
- name: add inherit EPGs from uEPGs
uEPG:
host: 192.168.1.1
username: "admin"
password: "pass"
validate_certs: no
tenant: "TNT_TNT"
ap: "APP_ANP"
bd: "BD_BD"
uepg: "{{ item.0.HOSTNAME }}"
uepg_attribute_ip: "{{ item.0.IPADDRESS }}"
inherited_epg: "{{ item.1 }}"
state: present
output_level: debug
with_subelements:
- "{{ list_uEPG.host_list }}"
- ROLELIST
ignore_errors: yes
register: query_result_present
- name: rm inherit EPGs from uEPGs
uEPG:
host: 192.168.1.1
username: "admin"
password: "pass"
validate_certs: no
tenant: "TNT_TNT"
ap: "APP_ANP"
bd: "BD_BD"
uepg: "{{ item.0.HOSTNAME }}"
uepg_attribute_ip: "{{ item.0.IPADDRESS }}"
inherited_epg: "{{ item.1 }}"
state: absent
output_level: debug
register: query_result_absent
with_subelements:
- "{{ list_uEPG.host_list }}"
- ROLELIST
ignore_errors: yes
- copy: content="{{ query_result_present | to_nice_json }}" dest=uepg_present.json
- copy: content="{{ query_result_absent | to_nice_json }}" dest=uepg_absent.json
## end add section
uEPG.py - the implementation of the module
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = r'''
---
TBD
'''
EXAMPLES = r'''
TBD
'''
RETURN = r'''
TBD
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec
def main():
argument_spec = aci_argument_spec()
argument_spec.update(
uepg=dict(type='str', aliases=['epg_name', 'name']), # Not required for querying all objects
bd=dict(type='str', aliases=['bd_name', 'bridge_domain']),
ap=dict(type='str', aliases=['app_profile', 'app_profile_name']), # Not required for querying all objects
tenant=dict(type='str', aliases=['tenant_name']), # Not required for querying all objects
description=dict(type='str', aliases=['descr']),
priority=dict(type='str', choices=['level1', 'level2', 'level3', 'unspecified']),
intra_epg_isolation=dict(choices=['enforced', 'unenforced']),
fwd_control=dict(type='str', choices=['none', 'proxy-arp']),
preferred_group=dict(type='bool'),
state=dict(type='str', default='query', choices=['present', 'absent', 'query']),
uepg_attribute_ip=dict(type='str'),
inherited_epg=dict(type='str'),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
['state', 'absent', ['ap', 'uepg', 'inherited_epg', 'tenant']],
['state', 'present', ['ap', 'uepg', 'uepg_attribute_ip', 'inherited_epg', 'tenant']],
],
)
aci = ACIModule(module)
uepg = module.params['uepg']
bd = module.params['bd']
description = module.params['description']
priority = module.params['priority']
intra_epg_isolation = module.params['intra_epg_isolation']
fwd_control = module.params['fwd_control']
preferred_group = aci.boolean(module.params['preferred_group'], 'include', 'exclude')
state = module.params['state']
tenant = module.params['tenant']
ap = module.params['ap']
uepg_attribute_ip = module.params['uepg_attribute_ip']
inherited_epg = module.params['inherited_epg']
inherited_epg_dn = 'uni/tn-{0}/ap-{1}/epg-{2}'.format(tenant, ap, inherited_epg)
uepg_dn = 'uni/tn-{0}/ap-{1}/epg-{2}'.format(tenant, ap, uepg)
to_del = 'deleted'
aci.construct_url(
root_class=dict(
aci_class='fvTenant',
aci_rn='tn-{0}'.format(tenant),
module_object=tenant,
target_filter={'name': tenant},
),
subclass_1=dict(
aci_class='fvAp',
aci_rn='ap-{0}'.format(ap),
module_object=ap,
target_filter={'name': ap},
),
subclass_2=dict(
aci_class='fvAEPg',
aci_rn='epg-{0}'.format(uepg),
module_object=uepg,
target_filter={'name': uepg},
),
child_classes=['fvRsBd', 'fvCrtrn', 'fvRsSecInherited'],
)
aci.get_existing()
if state == 'present':
aci.payload(
aci_class='fvAEPg',
class_config=dict(
name=uepg,
descr=description,
prio=priority,
pcEnfPref=intra_epg_isolation,
fwdCtrl=fwd_control,
isAttrBasedEPg='true',
prefGrMemb=preferred_group,
),
child_configs=[
dict(fvRsBd=dict(
attributes=dict(
#tnFvBDName=bd,
tnFvBDName=bd,
),
),
),
dict(fvRsSecInherited=dict(
attributes=dict(
# tnFvBDName=bd,
tDn=inherited_epg_dn,
),
),
),
dict(fvCrtrn=dict(
attributes=dict(
scope='scope-bd',
match="any",
name='default',
),
children=[dict(
fvIpAttr=dict(
attributes=dict(
ip=uepg_attribute_ip,
name='0',
),
),
)],
),
)
],
)
aci.get_diff(aci_class='fvAEPg')
aci.post_config()
elif state == 'absent':
aci.payload(
aci_class='fvAEPg',
class_config=dict(
dn=uepg_dn,
name=uepg,
descr=description,
prio=priority,
pcEnfPref=intra_epg_isolation,
fwdCtrl=fwd_control,
isAttrBasedEPg='true',
prefGrMemb=preferred_group,
),
child_configs=[
dict(fvRsSecInherited=dict(
attributes=dict(
annotation='',
tDn=inherited_epg_dn,
status=to_del,
),
),
)
],
)
aci.get_diff(aci_class='fvAEPg')
aci.post_config()
aci.exit_json()
if __name__ == "__main__":
main()
host_data_created.json - the data file used with info for the uEPGs
"host_list": [
{ "HOSTNAME": "host1","ENV": "os","IPADDRESS": "1.1.1.1","ROLELIST":[ "role_1", "type_1", "group_1", "role_12", "location_1", "domain_1"] },
{ "HOSTNAME": "host2","ENV": "os","IPADDRESS": "2.2.2.2","ROLELIST":[ "role_2", "type_2", "role_3", "sgroup_2", "location_3", "domain_3", "role_13"] },
{ "HOSTNAME": "host3","ENV": "os","IPADDRESS": "3.3.3.3","ROLELIST":[ "type_3", "sgroup_2", "location_1", "domain_4", "role_5"] },
{ "HOSTNAME": "host4": "os","IPADDRESS": "4.4.4.4","ROLELIST":[ "role_4", "type_5", "sgroup_3", "location_5", "domain_6", "role_6"] }
]
}
Key: {"fvAEPg": {"attributes": {"isAttrBasedEPg": "true"}, "children": [{"fvRsSecInherited": {"attributes": {"status": "deleted", "tDn": "uni/tn-sd/ap-APP_ANP/epg-role_1"}}}]}}
ACTUAL RESULTS
Run the playbook ansible_aci_send.yaml and you will see that one element from list named ROLELIST fails:
failed: [bdsol-aci16-apic1] (item=[{u'HOSTNAME': u'host1', u'IPADDRESS': u'1.1.1.1', u'ENV': os'}, u'role_1']) => {"ansible_loop_var": "item", "changed": false, "error": {"code": "822", "text": "naming property ('fvRsSecInherited.tDn') is not set."}, "filter_string": "?rsp-prop-include=config-only&rsp-subtree-class=fvCrtrn,fvRsBd,fvRsSecInherited&rsp-subtree=full", "imdata": [{"error": {"attributes": {"code": "822", "text": "naming property ('fvRsSecInherited.tDn') is not set."}}}]
Looking at what is being POSTed to APIC, we see that actually the Ansible is not sending the "tDn" element:
[Full request URI: http://bdsol-aci16-apic1/api/mo/uni/tn-sd/ap-APP_ANP/epg-role1.json]
Key: {"fvAEPg": {"attributes": {"isAttrBasedEPg": "true"}, "children": [{"fvRsSecInherited": {"attributes": {"status": "deleted"}}}]}}