Repository: ansible/ansible

Issue:
(P1)nxos_aaa_server_host: MEGA issue list
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
- nxos_aaa_server_host

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.4.0 (devel e2d6ecfa40) last updated 2017/07/21 14:49:47 (GMT -400)
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /root/agents-ci/ansible/lib/ansible
  executable location = /root/agents-ci/ansible/bin/ansible
  python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]
```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
Ubuntu 14.04

##### SUMMARY
<!--- Explain the problem briefly -->
Several issues
- simple play with only server_type and address set is not idempotent.
- simple play with only server_type and address set can't be removed (i.e state absent).
- play with encrypt type 0 is not idempotent
- nxapi transport is completely and fails with `command can't be piped message`

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
<!--- You can also paste gist.github.com links for larger files -->
https://gist.github.com/rahushen/e5b9e14808436a5387f498ceab19249c

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Plays apply cleanly.



Discussion:
cc @GGabriele @jedelman8 @mikewiebe @privateip @rcarrillocruz @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py b/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
--- a/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
+++ b/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
@@ -167,7 +167,7 @@ def execute_show_command(command, module, command_type='cli_show'):
         cmds = [command]
         body = run_commands(module, cmds)
     elif module.params['transport'] == 'nxapi':
-        cmds = [command]
+        cmds = {'command': command, 'output': 'text'}
         body = run_commands(module, cmds)
 
     return body
@@ -208,7 +208,7 @@ def get_aaa_host_info(module, server_type, address):
 
     body = execute_show_command(command, module, command_type='cli_show_ascii')
 
-    if body:
+    if body[0]:
         try:
             pattern = ('(acct-port \d+)|(timeout \d+)|(auth-port \d+)|'
                        '(key 7 "\w+")|( port \d+)')
@@ -217,9 +217,8 @@ def get_aaa_host_info(module, server_type, address):
                                                     'auth-port': 'auth_port',
                                                     'port': 'tacacs_port',
                                                     'timeout': 'host_timeout'})
-            if aaa_host_info:
-                aaa_host_info['server_type'] = server_type
-                aaa_host_info['address'] = address
+            aaa_host_info['server_type'] = server_type
+            aaa_host_info['address'] = address
         except TypeError:
             return {}
     else:
diff --git a/lib/ansible/modules/network/nxos/nxos_interface.py b/lib/ansible/modules/network/nxos/nxos_interface.py
--- a/lib/ansible/modules/network/nxos/nxos_interface.py
+++ b/lib/ansible/modules/network/nxos/nxos_interface.py
@@ -214,22 +214,21 @@ def get_manual_interface_attributes(interface, module):
     """
 
     if get_interface_type(interface) == 'svi':
-        command = 'show interface {0}'.format(interface)
+        command = 'show run interface {0} all'.format(interface)
         try:
-            body = run_commands(module, [command])[0]
+            # body = run_commands(module, [command])[0]
+            body = execute_show_command(command, module)[0]
         except IndexError:
             return None
-
         if body:
             command_list = body.split('\n')
             desc = None
-            admin_state = 'up'
+            admin_state = 'down'
             for each in command_list:
-                if 'Description:' in each:
-                    line = each.split('Description:')
-                    desc = line[1].strip().split('MTU')[0].strip()
-                elif 'Administratively down' in each:
-                    admin_state = 'down'
+                if 'description' in each:
+                    desc = each.lstrip().split("description")[1].lstrip()
+                elif 'no shutdown' in each:
+                    admin_state = 'up'
 
             return dict(description=desc, admin_state=admin_state)
     else:
diff --git a/lib/ansible/modules/network/nxos/nxos_pim_interface.py b/lib/ansible/modules/network/nxos/nxos_pim_interface.py
--- a/lib/ansible/modules/network/nxos/nxos_pim_interface.py
+++ b/lib/ansible/modules/network/nxos/nxos_pim_interface.py
@@ -147,7 +147,7 @@
             "ip pim neighbor-policy test"]
 '''
 
-
+import re
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.nxos import get_config, load_config, run_commands
 from ansible.module_utils.nxos import nxos_argument_spec, check_args
@@ -246,6 +246,29 @@ def get_interface_mode(interface, intf_type, module):
     return mode
 
 
+def get_jp_policy_out(module, interface):
+    '''
+    This is a workaround for an nxos structured output problem.
+    The 'show ip pim interface' command does not display jp_policy_out
+    information properly for all supported nxos platforms when using
+    structured output.  This method uses the show running information to
+    mitigate the problem.
+    '''
+    command = 'sh run interface {0} all | grep \"jp.*out\"'.format(interface)
+    name = None
+    try:
+        body = execute_show_command(command, module, text=True)[0]
+    except IndexError:
+        return name
+
+    if body:
+        mo = re.search(r'ip pim jp-policy\s+(\S+)\s+out', body)
+        if mo:
+            name = mo.group(1)
+
+    return name
+
+
 def get_pim_interface(module, interface):
     pim_interface = {}
     command = 'show ip pim interface {0}'.format(interface)
@@ -255,50 +278,47 @@ def get_pim_interface(module, interface):
         if 'not running' not in body[0]:
             body = execute_show_command(command, module)
 
+    # Some nxos platforms have the TABLE_vrf/ROW_vrf key and some don't
     try:
         get_data = body[0]['TABLE_vrf']['ROW_vrf']['TABLE_iod']['ROW_iod']
+    except (KeyError, AttributeError, TypeError, IndexError):
+        try:
+            get_data = body[0]['TABLE_iod']['ROW_iod']
+        except (KeyError, AttributeError, TypeError, IndexError):
+            return pim_interface
 
-        if isinstance(get_data.get('dr-priority'), list):
-            pim_interface['dr_prio'] = get_data.get('dr-priority')[0]
-        else:
-            pim_interface['dr_prio'] = str(get_data.get('dr-priority'))
+    if isinstance(get_data.get('dr-priority'), list):
+        pim_interface['dr_prio'] = get_data.get('dr-priority')[0]
+    else:
+        pim_interface['dr_prio'] = str(get_data.get('dr-priority'))
 
-        hello_interval = get_data.get('hello-interval-sec')
-        if hello_interval:
-            hello_interval_msec = int(get_data.get('hello-interval-sec')) * 1000
+    hello_interval = get_data.get('hello-interval-sec')
+    if hello_interval:
+        hello_interval_msec = int(get_data.get('hello-interval-sec')) * 1000
         pim_interface['hello_interval'] = str(hello_interval_msec)
 
-        border = get_data.get('is-border')
-        if border == 'true':
-            pim_interface['border'] = True
-        elif border == 'false':
-            pim_interface['border'] = False
-
-        isauth = get_data.get('isauth-config')
-        if isauth == 'true':
-            pim_interface['isauth'] = True
-        elif isauth == 'false':
-            pim_interface['isauth'] = False
-
-        pim_interface['neighbor_policy'] = get_data.get('nbr-policy-name')
-        if pim_interface['neighbor_policy'] == 'none configured':
-            pim_interface['neighbor_policy'] = None
-
-        jp_in_policy = get_data.get('jp-in-policy-name')
-        pim_interface['jp_policy_in'] = jp_in_policy
-        if jp_in_policy == 'none configured':
-            pim_interface['jp_policy_in'] = None
-
-        if isinstance(get_data.get('jp-out-policy-name'), string_types):
-            pim_interface['jp_policy_out'] = get_data.get('jp-out-policy-name')
-        else:
-            pim_interface['jp_policy_out'] = get_data.get('jp-out-policy-name')[0]
+    border = get_data.get('is-border')
+    if border == 'true':
+        pim_interface['border'] = True
+    elif border == 'false':
+        pim_interface['border'] = False
 
-        if pim_interface['jp_policy_out'] == 'none configured':
-            pim_interface['jp_policy_out'] = None
+    isauth = get_data.get('isauth-config')
+    if isauth == 'true':
+        pim_interface['isauth'] = True
+    elif isauth == 'false':
+        pim_interface['isauth'] = False
 
-    except (KeyError, AttributeError, TypeError, IndexError):
-        return {}
+    pim_interface['neighbor_policy'] = get_data.get('nbr-policy-name')
+    if pim_interface['neighbor_policy'] == 'none configured':
+        pim_interface['neighbor_policy'] = None
+
+    jp_in_policy = get_data.get('jp-in-policy-name')
+    pim_interface['jp_policy_in'] = jp_in_policy
+    if jp_in_policy == 'none configured':
+        pim_interface['jp_policy_in'] = None
+
+    pim_interface['jp_policy_out'] = get_jp_policy_out(module, interface)
 
     body = get_config(module, flags=['interface {0}'.format(interface)])
 


Test patch:
diff --git a/test/integration/nxos.yaml b/test/integration/nxos.yaml
--- a/test/integration/nxos.yaml
+++ b/test/integration/nxos.yaml
@@ -353,6 +353,13 @@
       rescue:
         - set_fact: test_failed=true
 
+    - block:
+      - include_role:
+          name: nxos_pim_interface
+        when: "limit_to in ['*', 'nxos_pim_interface']"
+      rescue:
+        - set_fact: test_failed=true
+
 ###########
     - debug: var=failed_modules
       when: test_failed
diff --git a/test/integration/targets/nxos_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_interface/tests/common/sanity.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_interface/tests/common/sanity.yaml
@@ -0,0 +1,131 @@
+---
+- debug: msg="START TRANSPORT:{{ connection.transport }} nxos_interface sanity test"
+
+- set_fact: testint="{{ nxos_int1 }}"
+
+- name: "Setup: Enable feature interface-vlan"
+  nxos_feature:
+    feature: interface-vlan
+    state: enabled
+    provider: "{{ connection }}"
+  ignore_errors: yes
+
+- name: "Setup: Put interface {{ testint }} into a default state"
+  nxos_config: &intcleanup
+    lines:
+      - "default interface {{ testint }}"
+    provider: "{{ connection }}"
+  ignore_errors: yes
+
+- name: "Setup: Remove possibly existing vlan interfaces"
+  nxos_config: &vlanintcleanup
+    lines:
+      - "no interface vlan 2"
+      - "no interface vlan 710"
+      - "no interface vlan 711"
+      - "no interface vlan 712"
+    provider: "{{ connection }}"
+  ignore_errors: yes
+
+- block:
+  - name: "Configure layer3 params"
+    nxos_interface: &l3config
+      interface: "{{ testint }}"
+      mode: layer3
+      description: 'Configured by Ansible - Layer3'
+      admin_state: 'up'
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: &true
+      that:
+        - "result.changed == true"
+
+  - name: "Check Idempotence"
+    nxos_interface: *l3config
+    register: result
+
+  - assert: &false
+      that:
+        - "result.changed == false"
+
+  - name: "Configure layer2 params"
+    nxos_interface: &l2config
+      interface: "{{ testint }}"
+      mode: layer2
+      description: 'Configured by Ansible - Layer2'
+      admin_state: 'down'
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_interface: *l2config
+    register: result
+
+  - assert: *false
+
+  - name: Create VLAN Interfaces
+    nxos_interface: &createvlans
+      interface: "{{ item.os_svi_int }}"
+      description: "{{ item.os_svi_desc }}"
+      provider: "{{ connection }}"
+    with_items: &vlanitems
+      - {os_svi_int: vlan2, os_svi_desc: SVI_VLAN2}
+      - {os_svi_int: vlan710, os_svi_desc: SVI_VLAN710}
+      - {os_svi_int: vlan711, os_svi_desc: SVI_VLAN711}
+      - {os_svi_int: vlan712, os_svi_desc: SVI_VLAN712}
+    register: result
+
+  - assert: *true
+
+  - name: Configure Required SVI
+    nxos_ip_interface: &addips
+      interface: "{{ item.os_svi_int }}"
+      addr: "{{ item.ipv4_addr }}"
+      mask: "{{ item.ipv4_mask }}"
+      version: "{{ item.ipv4_ver }}"
+      provider: "{{ connection }}"
+    with_items: &vlanips
+      - {os_svi_int: vlan2, ipv4_addr: 192.168.2.1, ipv4_mask: 24, ipv4_ver: v4}
+      - {os_svi_int: vlan710, ipv4_addr: 192.168.3.1, ipv4_mask: 24, ipv4_ver: v4}
+      - {os_svi_int: vlan711, ipv4_addr: 192.168.4.1, ipv4_mask: 24, ipv4_ver: v4}
+      - {os_svi_int: vlan712, ipv4_addr: 192.168.5.1, ipv4_mask: 24, ipv4_ver: v4}
+    register: result
+
+  - assert: *true
+
+  - name: Create VLAN Interfaces Idempotence Check
+    nxos_interface: *createvlans
+    with_items: *vlanitems
+    register: result
+
+  - assert: *false
+
+  - name: Configure Required SVI Idempotence Check
+    nxos_ip_interface: *addips
+    with_items: *vlanips
+    register: result
+
+  - assert: *false
+
+  rescue:
+  - name: "Set interface back to default"
+    nxos_config: *intcleanup
+    ignore_errors: yes
+
+  - name: "Remove vlan interfaces"
+    nxos_config: *vlanintcleanup
+
+  - name: "Setup: Disable feature interface-vlan"
+    nxos_feature:
+      feature: interface-vlan
+      state: disabled
+      provider: "{{ connection }}"
+    ignore_errors: yes
+
+  always:
+  - debug: msg="END TRANSPORT:{{ connection.transport }} nxos_interface sanity test"
diff --git a/test/integration/targets/nxos_pim_interface/defaults/main.yaml b/test/integration/targets/nxos_pim_interface/defaults/main.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/nxos_pim_interface/meta/main.yml b/test/integration/targets/nxos_pim_interface/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+  - prepare_nxos_tests
diff --git a/test/integration/targets/nxos_pim_interface/tasks/cli.yaml b/test/integration/targets/nxos_pim_interface/tasks/cli.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tasks/cli.yaml
@@ -0,0 +1,15 @@
+---
+- name: collect all cli test cases
+  find:
+    paths: "{{ role_path }}/tests/cli"
+    patterns: "{{ testcase }}.yaml"
+  register: test_cases
+
+- name: set test_items
+  set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case
+  include: "{{ test_case_to_run }}"
+  with_items: "{{ test_items }}"
+  loop_control:
+    loop_var: test_case_to_run
diff --git a/test/integration/targets/nxos_pim_interface/tasks/main.yaml b/test/integration/targets/nxos_pim_interface/tasks/main.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tasks/main.yaml
@@ -0,0 +1,7 @@
+---
+# Use block to ensure that both cli and nxapi tests
+# will run even if there are failures or errors.
+- block:
+  - { include: cli.yaml, tags: ['cli'] }
+  always:
+  - { include: nxapi.yaml, tags: ['nxapi'] }
diff --git a/test/integration/targets/nxos_pim_interface/tasks/nxapi.yaml b/test/integration/targets/nxos_pim_interface/tasks/nxapi.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tasks/nxapi.yaml
@@ -0,0 +1,28 @@
+---
+- name: collect all nxapi test cases
+  find:
+    paths: "{{ role_path }}/tests/nxapi"
+    patterns: "{{ testcase }}.yaml"
+  register: test_cases
+
+- name: set test_items
+  set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: enable nxapi
+  nxos_config:
+    lines:
+      - feature nxapi
+      - nxapi http port 80
+    provider: "{{ cli }}"
+
+- name: run test case
+  include: "{{ test_case_to_run }}"
+  with_items: "{{ test_items }}"
+  loop_control:
+    loop_var: test_case_to_run
+
+- name: disable nxapi
+  nxos_config:
+    lines:
+      - no feature nxapi
+    provider: "{{ cli }}"
diff --git a/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml
@@ -0,0 +1,4 @@
+---
+- set_fact: connection="{{ cli }}"
+
+- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml
@@ -0,0 +1,102 @@
+---
+- debug: msg="START TRANSPORT:{{ connection.transport }} nxos_pim_interface sanity test"
+
+- name: "Disable feature PIM"
+  nxos_feature: &disable_feature
+    feature: pim
+    state: disabled
+    provider: "{{ connection }}"
+
+- name: "Enable feature PIM"
+  nxos_feature:
+    feature: pim
+    state: enabled
+    provider: "{{ connection }}"
+
+- set_fact: testint="{{ nxos_int1 }}"
+
+- name: "Setup: Put interface {{ testint }} into a default state"
+  nxos_config:
+    lines:
+      - "default interface {{ testint }}"
+    provider: "{{ connection }}"
+  ignore_errors: yes
+
+- name: "Ensure {{testint}} is layer3"
+  nxos_interface:
+    interface: "{{ testint }}"
+    mode: layer3
+    description: 'Configured by Ansible - Layer3'
+    admin_state: 'up'
+    state: present
+    provider: "{{ connection }}"
+
+- name: Configure nxos_pim_interface state absent
+  nxos_pim_interface:
+    interface: "{{ testint }}"
+    state: absent
+    provider: "{{ connection }}"
+
+- block:
+  - name: configure pim interface
+    nxos_pim_interface: &config
+      interface: "{{ testint }}"
+      dr_prio: 10
+      hello_interval: 40
+      border: 'false'
+      neighbor_policy: 'ansible_policy'
+      neighbor_type: 'prefix'
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: &true
+      that:
+        - "result.changed == true"
+
+  - name: Check idempotence
+    nxos_pim_interface: *config
+    register: result
+
+  - assert: &false
+      that:
+        - "result.changed == false"
+
+  - name: configure gp policy and type
+    nxos_pim_interface: &configjp
+      interface: "{{ testint }}"
+      jp_policy_in: JPIN
+      jp_policy_out: JPOUT
+      jp_type_in: routemap
+      jp_type_out: routemap
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: Check idempotence
+    nxos_pim_interface: *configjp
+    register: result
+
+  - assert: *false
+
+  - name: configure state default
+    nxos_pim_interface: &configdefault
+      interface: "{{ testint }}"
+      state: default
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: Check idempotence
+    nxos_pim_interface: *configdefault
+    register: result
+
+  - assert: *false
+
+  always:
+  - name: "Disable feature PIM"
+    nxos_feature: *disable_feature
+
+- debug: msg="END TRANSPORT:{{ connection.transport }} nxos_pim_interface sanity test"
diff --git a/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml
@@ -0,0 +1,4 @@
+---
+- set_fact: connection="{{ nxapi }}"
+
+- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/units/modules/network/nxos/fixtures/nxos_pim_interface/sh_run_interface_eth2_1_all b/test/units/modules/network/nxos/fixtures/nxos_pim_interface/sh_run_interface_eth2_1_all
new file mode 100644
diff --git a/test/units/modules/network/nxos/test_nxos_pim_interface.py b/test/units/modules/network/nxos/test_nxos_pim_interface.py
--- a/test/units/modules/network/nxos/test_nxos_pim_interface.py
+++ b/test/units/modules/network/nxos/test_nxos_pim_interface.py
@@ -62,12 +62,12 @@ def load_from_file(*args, **kwargs):
         self.run_commands.side_effect = load_from_file
 
     def test_nxos_pim_interface_present(self):
-        set_module_args(dict(interface='eth2/1', dr_prio=10, hello_interval=40, sparse=True, border=True))
+        set_module_args(dict(interface='eth2/1', dr_prio=10, hello_interval=40, sparse=True, border=False))
         self.execute_module(
             changed=True,
             commands=[
                 'interface eth2/1', 'ip pim dr-priority 10', 'ip pim hello-interval 40000',
-                'ip pim sparse-mode', 'ip pim border']
+                'ip pim sparse-mode', 'no ip pim border']
         )
 
     def test_nxos_pim_interface_jp(self):