Repository: ansible/ansible

Issue:
aci_interface_policy_leaf_policy_group query fails
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME

aci_interface_policy_leaf_policy_group.py

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]

```

##### CONFIGURATION
No change to config

##### OS / ENVIRONMENT
Ansible OS: Ubuntu 16.04LTS

Target OS: Cisco ACI v2.3(1f)

##### SUMMARY
The state "query" option does not work and instead outputs an error.

##### STEPS TO REPRODUCE
Used the below playbook against our lab ACI platform (full command to run the playbook in "Actual results" section):


```
---

  - name: PLAY - Leaf Policy Group viewer
    hosts: aci
    connection: local
    gather_facts: no


    tasks:
    
    - name: Query Leaf policy group
      aci_interface_policy_leaf_policy_group:
        hostname: '{{ ansible_host }}'
        username: '{{ provider_cli.username }}'
        password: '{{ provider_cli.password }}'
        state: query
        validate_certs: no
        
      register: imdata_leafpolgroup
      
    - debug:
        msg: "The response was: {{ imdata_leafpolgroup }}"

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS

Expected the output to contain a list of all the aci interface policy leaf policy groups.

##### ACTUAL RESULTS
Running the play fails with a module failure error message:
 
```
vagrant@ubuntu-xenial:~/vagrant_data/ansible$ ansible-playbook -i inventory_test aci-interface_policy_leaf_policy_group.yml --ask-vault-pass -vvvv
ansible-playbook 2.5.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
Using /etc/ansible/ansible.cfg as config file
Vault password: 
setting up inventory plugins
Parsed /home/vagrant/vagrant_data/ansible/inventory_test inventory source with ini plugin
Loading callback plugin default of type stdout, v2.0 from /usr/lib/python2.7/dist-packages/ansible/plugins/callback/default.pyc

PLAYBOOK: aci-interface_policy_leaf_policy_group.yml **********************************************************************************************************************************************
1 plays in aci-interface_policy_leaf_policy_group.yml

PLAY [PLAY - Leaf Policy Group viewer] ************************************************************************************************************************************************************
META: ran handlers
Trying secret <ansible.parsing.vault.PromptVaultSecret object at 0x7fb9ff1bf6d0> for vault_id=default

TASK [Query Leaf policy group] ********************************************************************************************************************************************************************
task path: /home/vagrant/vagrant_data/ansible/aci-interface_policy_leaf_policy_group.yml:11
Using module file /usr/lib/python2.7/dist-packages/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py
<10.112.129.173> ESTABLISH LOCAL CONNECTION FOR USER: vagrant
<10.112.129.173> EXEC /bin/sh -c 'echo ~ && sleep 0'
<10.112.129.173> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226 `" && echo ansible-tmp-1526654733.34-184548725007226="` echo /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226 `" ) && sleep 0'
<10.112.129.173> PUT /home/vagrant/.ansible/tmp/ansible-local-2790PDn11a/tmp2O3rgw TO /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226/aci_interface_policy_leaf_policy_group.py
<10.112.129.173> EXEC /bin/sh -c 'chmod u+x /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226/ /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226/aci_interface_policy_leaf_policy_group.py && sleep 0'
<10.112.129.173> EXEC /bin/sh -c '/usr/bin/python /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226/aci_interface_policy_leaf_policy_group.py && sleep 0'
<10.112.129.173> EXEC /bin/sh -c 'rm -f -r /home/vagrant/.ansible/tmp/ansible-tmp-1526654733.34-184548725007226/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_v6lBN_/ansible_module_aci_interface_policy_leaf_policy_group.py", line 507, in <module>
    main()
  File "/tmp/ansible_v6lBN_/ansible_module_aci_interface_policy_leaf_policy_group.py", line 349, in main
    aci_class=aci_class_name,
UnboundLocalError: local variable 'aci_class_name' referenced before assignment

fatal: [lab-aci]: FAILED! => {
    "changed": false, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_v6lBN_/ansible_module_aci_interface_policy_leaf_policy_group.py\", line 507, in <module>\n    main()\n  File \"/tmp/ansible_v6lBN_/ansible_module_aci_interface_policy_leaf_policy_group.py\", line 349, in main\n    aci_class=aci_class_name,\nUnboundLocalError: local variable 'aci_class_name' referenced before assignment\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}
        to retry, use: --limit @/home/vagrant/vagrant_data/ansible/aci-interface_policy_leaf_policy_group.retry

PLAY RECAP ****************************************************************************************************************************************************************************************
lab-aci                    : ok=0    changed=0    unreachable=0    failed=1   


```



Discussion:
Files identified in the description:
* [lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @brunocalogero @dagwieers @jmcgill298 @schunduri
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
From the code I can see what is going on. The `lag_type` parameter was not provided, and it is required to decide the correct class name for the URI, even when doing a query.

@brunocalogero @jmcgill298 Do you agree we should make this parameter a required parameter even when doing queries ?

The code specifies:
```python
lag_type=dict(type='str', aliases=['lag_type_name'], choices=['leaf', 'link', 'node']),   # Not required for querying all objects
```
but I think that's incorrect for this module. The **lag_type** parameter is always required.

I noticed the examples did not include any query-examples, and even the module's integration tests lack query tests for all types, probably because that won't work anyway.

Resolving patch:
diff --git a/lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py b/lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py
--- a/lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py
+++ b/lib/ansible/modules/network/aci/aci_interface_policy_leaf_policy_group.py
@@ -42,6 +42,7 @@
    - C(node) for Virtual Port Channel (VPC)
    aliases: [ lag_type_name ]
    choices: [ leaf, link, node ]
+   required: yes
  link_level_policy:
    description:
    - Choice of link_level_policy to be used as part of the leaf policy group to be created.
@@ -122,9 +123,9 @@
     host: apic
     username: admin
     password: SomeSecretPassword
+    lag_type: link
     policy_group: policygroupname
     description: policygroupname description
-    lag_type: link
     link_level_policy: whateverlinklevelpolicy
     fibre_channel_interface_policy: whateverfcpolicy
     state: present
@@ -134,8 +135,8 @@
     host: apic
     username: admin
     password: SomeSecretPassword
-    policy_group: policygroupname
     lag_type: node
+    policy_group: policygroupname
     link_level_policy: whateverlinklevelpolicy
     fibre_channel_interface_policy: whateverfcpolicy
     state: present
@@ -145,19 +146,36 @@
     host: apic
     username: admin
     password: SomeSecretPassword
-    policy_group: policygroupname
     lag_type: leaf
+    policy_group: policygroupname
     link_level_policy: whateverlinklevelpolicy
     fibre_channel_interface_policy: whateverfcpolicy
     state: present
 
-- name: Delete an Interface policy Leaf Policy Group
+- name: Query all Leaf Access Port Policy Groups of type link
   aci_interface_policy_leaf_policy_group:
     host: apic
     username: admin
     password: SomeSecretPassword
+    lag_type: link
+    state: query
+
+- name: Query a specific Lead Access Port Policy Group
+  aci_interface_policy_leaf_policy_group:
+    host: apic
+    username: admin
+    password: SomeSecretPassword
+    lag_type: leaf
     policy_group: policygroupname
+    state: query
+
+- name: Delete an Interface policy Leaf Policy Group
+  aci_interface_policy_leaf_policy_group:
+    host: apic
+    username: admin
+    password: SomeSecretPassword
     lag_type: type_name
+    policy_group: policygroupname
     state: absent
 '''
 
@@ -277,7 +295,7 @@ def main():
         description=dict(type='str', aliases=['descr']),
         # NOTE: Since this module needs to include both infra:AccBndlGrp (for PC and VPC) and infra:AccPortGrp (for leaf access port policy group):
         # NOTE: I'll allow the user to make the choice here (link(PC), node(VPC), leaf(leaf-access port policy group))
-        lag_type=dict(type='str', aliases=['lag_type_name'], choices=['leaf', 'link', 'node']),  # Not required for querying all objects
+        lag_type=dict(type='str', required=True, aliases=['lag_type_name'], choices=['leaf', 'link', 'node']),
         link_level_policy=dict(type='str', aliases=['link_level_policy_name']),
         cdp_policy=dict(type='str', aliases=['cdp_policy_name']),
         mcp_policy=dict(type='str', aliases=['mcp_policy_name']),
@@ -301,8 +319,8 @@ def main():
         argument_spec=argument_spec,
         supports_check_mode=True,
         required_if=[
-            ['state', 'absent', ['lag_type', 'policy_group']],
-            ['state', 'present', ['lag_type', 'policy_group']],
+            ['state', 'absent', ['policy_group']],
+            ['state', 'present', ['policy_group']],
         ],
     )
 
@@ -334,7 +352,7 @@ def main():
             name=policy_group,
             descr=description,
         )
-    elif lag_type == 'link' or lag_type == 'node':
+    elif lag_type in ('link', 'node'):
         aci_class_name = 'infraAccBndlGrp'
         dn_name = 'accbundle'
         class_config_dict = dict(


Test patch: