Repository: ansible/ansible

Issue:
nxos_udld_interface issues
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Insert below this comment the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
nxos_udld_interface

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0 (devel fed20b825f) last updated 2018/02/15 12:51:12 (GMT -400)
  config file = /etc/ansible/ansible.cfg
  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.
-->
Ansible Server : Ubuntu 14.04
Device: N9K running 7.0(3)F3(4)

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
There is an issue with nxos_udld_interface.
On the platform, we cannot properly differentiate between copper and fiber ports an so the commands to configure the udld mode on the interface cannot be determined properly. The remedy was to run one set of commands and if they throw errors, run the 2nd set. However, on testing with newer platforms and newer versions, it is found that, the 1st set of commands do not always throw errors and they just get silently ignored doing nothing. So the 2nd set of commands are never run and the configuration is staying as before which is incorrect.

Note: I will open a PR shortly to address the above issue


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_udld_interface.py b/lib/ansible/modules/network/nxos/nxos_udld_interface.py
--- a/lib/ansible/modules/network/nxos/nxos_udld_interface.py
+++ b/lib/ansible/modules/network/nxos/nxos_udld_interface.py
@@ -110,27 +110,11 @@
 '''
 
 
-from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands
-from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec
+from ansible.module_utils.network.nxos.nxos import load_config, run_commands
+from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
 from ansible.module_utils.basic import AnsibleModule
 
 
-def execute_show_command(command, module, command_type='cli_show'):
-    device_info = get_capabilities(module)
-    network_api = device_info.get('network_api', 'nxapi')
-
-    if network_api == 'cliconf':
-        if 'show run' not in command:
-            command += ' | json'
-        cmds = [command]
-        body = run_commands(module, cmds)
-    elif network_api == 'nxapi':
-        cmds = [command]
-        body = run_commands(module, cmds)
-
-    return body
-
-
 def flatten_list(command_lists):
     flat_command_list = []
     for command in command_lists:
@@ -142,75 +126,61 @@ def flatten_list(command_lists):
 
 
 def get_udld_interface(module, interface):
-    command = 'show udld {0}'.format(interface)
+    command = 'show run udld all | section ' + interface.title() + '$'
     interface_udld = {}
     mode = None
+    mode_str = None
     try:
-        body = execute_show_command(command, module)[0]
-        table = body['TABLE_interface']['ROW_interface']
-
-        status = str(table.get('mib-port-status', None))
-        # Note: 'mib-aggresive-mode' is NOT a typo
-        agg = str(table.get('mib-aggresive-mode', 'disabled'))
-
-        if agg == 'enabled':
+        body = run_commands(module, [{'command': command, 'output': 'text'}])[0]
+        if 'aggressive' in body:
             mode = 'aggressive'
-        else:
-            mode = status
-
+            mode_str = 'aggressive'
+        elif 'no udld enable' in body:
+            mode = 'disabled'
+            mode_str = 'no udld enable'
+        elif 'no udld disable' in body:
+            mode = 'enabled'
+            mode_str = 'no udld disable'
+        elif 'udld disable' in body:
+            mode = 'disabled'
+            mode_str = 'udld disable'
+        elif 'udld enable' in body:
+            mode = 'enabled'
+            mode_str = 'udld enable'
         interface_udld['mode'] = mode
 
     except (KeyError, AttributeError, IndexError):
         interface_udld = {}
 
-    return interface_udld
+    return interface_udld, mode_str
 
 
 def get_commands_config_udld_interface1(delta, interface, module, existing):
     commands = []
-    if delta:
-        mode = delta['mode']
-        if mode == 'aggressive':
-            command = 'udld aggressive'
-        if mode == 'enabled':
-            command = 'no udld aggressive ; udld enable'
-        elif mode == 'disabled':
-            command = 'no udld aggressive ; no udld enable'
-    if command:
-        commands.append(command)
-        commands.insert(0, 'interface {0}'.format(interface))
+    mode = delta['mode']
+    if mode == 'aggressive':
+        commands.append('udld aggressive')
+    else:
+        commands.append('no udld aggressive')
+    commands.insert(0, 'interface {0}'.format(interface))
 
     return commands
 
 
 def get_commands_config_udld_interface2(delta, interface, module, existing):
     commands = []
-    if delta:
-        mode = delta['mode']
-        if mode == 'aggressive':
-            command = 'udld aggressive'
-        if mode == 'enabled':
-            command = 'no udld aggressive ; no udld disable'
-        elif mode == 'disabled':
-            command = 'no udld aggressive ; udld disable'
-    if command:
-        commands.append(command)
-        commands.insert(0, 'interface {0}'.format(interface))
-
-    return commands
-
-
-def get_commands_remove_udld_interface1(delta, interface, module, existing):
-    commands = []
-
-    if delta:
-        mode = delta['mode']
-        if mode == 'aggressive':
-            command = 'no udld aggressive'
-        if mode == 'enabled':
-            command = 'no udld enable'
-        elif mode == 'disabled':
+    existing, mode_str = get_udld_interface(module, interface)
+    mode = delta['mode']
+    if mode == 'enabled':
+        if mode_str == 'no udld enable':
             command = 'udld enable'
+        else:
+            command = 'no udld disable'
+    else:
+        if mode_str == 'no udld disable':
+            command = 'udld disable'
+        else:
+            command = 'no udld enable'
     if command:
         commands.append(command)
         commands.insert(0, 'interface {0}'.format(interface))
@@ -218,17 +188,24 @@ def get_commands_remove_udld_interface1(delta, interface, module, existing):
     return commands
 
 
-def get_commands_remove_udld_interface2(delta, interface, module, existing):
+def get_commands_remove_udld_interface(delta, interface, module, existing):
     commands = []
+    existing, mode_str = get_udld_interface(module, interface)
 
-    if delta:
-        mode = delta['mode']
-        if mode == 'aggressive':
-            command = 'no udld aggressive'
+    mode = delta['mode']
+    if mode == 'aggressive':
+        command = 'no udld aggressive'
+    else:
         if mode == 'enabled':
-            command = 'udld disable'
+            if mode_str == 'udld enable':
+                command = 'no udld enable'
+            else:
+                command = 'udld disable'
         elif mode == 'disabled':
-            command = 'no udld disable'
+            if mode_str == 'no udld disable':
+                command = 'udld disable'
+            else:
+                command = 'no udld enable'
     if command:
         commands.append(command)
         commands.insert(0, 'interface {0}'.format(interface))
@@ -256,61 +233,54 @@ def main():
     state = module.params['state']
 
     proposed = dict(mode=mode)
-    existing = get_udld_interface(module, interface)
+    existing, mode_str = get_udld_interface(module, interface)
     end_state = existing
 
     delta = dict(set(proposed.items()).difference(existing.items()))
 
     changed = False
     commands = []
+    cmds = []
     if state == 'present':
         if delta:
             command = get_commands_config_udld_interface1(delta, interface,
                                                           module, existing)
             commands.append(command)
-    elif state == 'absent':
+            cmds = flatten_list(commands)
+            if module.check_mode:
+                module.exit_json(changed=True, commands=cmds)
+            else:
+                changed = True
+                load_config(module, cmds)
+
+            if delta['mode'] == 'enabled' or delta['mode'] == 'disabled':
+                commands = []
+                command = get_commands_config_udld_interface2(delta, interface,
+                                                              module, existing)
+                commands.append(command)
+                cmds = flatten_list(commands)
+                if module.check_mode:
+                    module.exit_json(changed=True, commands=cmds)
+                else:
+                    load_config(module, cmds)
+
+    else:
         common = set(proposed.items()).intersection(existing.items())
         if common:
-            command = get_commands_remove_udld_interface1(
+            command = get_commands_remove_udld_interface(
                 dict(common), interface, module, existing
             )
-            commands.append(command)
-
-    cmds = flatten_list(commands)
-    if cmds:
-        if module.check_mode:
-            module.exit_json(changed=True, commands=cmds)
-        else:
-            changed = True
-            # set the return_error to True for load_config
-            msgs = load_config(module, cmds, True)
-            # since there are multiple commands sent simultaneously
-            # the output will have one error code for each command.
-            # For commands which are successful, it is empty
-            for item in msgs:
-                if item:
-                    err_str = ''
-                    if isinstance(item, list) and item['msg']:
-                        err_str = item['msg']
-                    elif isinstance(item, str):
-                        err_str = item
-                    if 'rejecting a config that is valid only for' in err_str:
-                        commands = []
-                        if state == 'present':
-                            command = get_commands_config_udld_interface2(delta, interface,
-                                                                          module, existing)
-                        elif state == 'absent':
-                            command = get_commands_remove_udld_interface2(
-                                dict(common), interface, module, existing
-                            )
-                        commands.append(command)
-
-                        cmds = flatten_list(commands)
-                        load_config(module, cmds)
-
-            end_state = get_udld_interface(module, interface)
-            if 'configure' in cmds:
-                cmds.pop(0)
+            cmds = flatten_list(commands)
+            if module.check_mode:
+                module.exit_json(changed=True, commands=cmds)
+            else:
+                changed = True
+                load_config(module, cmds)
+
+    if not module.check_mode:
+        end_state, mode_str = get_udld_interface(module, interface)
+        if 'configure' in cmds:
+            cmds.pop(0)
 
     results = {}
     results['proposed'] = proposed


Test patch:
diff --git a/test/integration/targets/nxos_udld_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_udld_interface/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_udld_interface/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_udld_interface/tests/common/sanity.yaml
@@ -4,10 +4,13 @@
   when: ansible_connection == "local"
 
 - set_fact: udld_run="true"
+- set_fact: udld_enable="true"
 - set_fact: udld_run="false"
-  when: ((platform is search('N9K-F')) and (imagetag and (imagetag is version('F3', 'lt'))))
+  when: ((platform is search('N9K-F')) and (imagetag and (imagetag is version_compare('F3', 'lt'))))
 - set_fact: udld_run="false"
   when: titanium
+- set_fact: udld_enable="false"
+  when: imagetag and (imagetag is version_compare('N1', 'eq'))
 
 # Select interface for test
 - set_fact: intname="{{ nxos_int1 }}"
@@ -46,21 +49,53 @@
       that:
         - "result.changed == false"
 
-  - name: ensure interface has mode enabled
-    nxos_udld_interface: &conf2
-      interface: "{{ intname }}"
-      mode: enabled
-      state: present
-      provider: "{{ connection }}"
-    register: result
+  - block:
 
-  - assert: *true
+    - name: ensure interface has mode enabled
+      nxos_udld_interface: &conf2
+        interface: "{{ intname }}"
+        mode: enabled
+        state: present
+        provider: "{{ connection }}"
+      register: result
 
-  - name: "Conf2 Idempotence"
-    nxos_udld_interface: *conf2
-    register: result
+    - assert: *true
+
+    - name: "Conf2 Idempotence"
+      nxos_udld_interface: *conf2
+      register: result
+
+    - assert: *false
+
+    - name: ensure interface has mode aggressive
+      nxos_udld_interface: *conf1
+      register: result
+
+    - assert: *true
+
+    - name: "Conf1 Idempotence"
+      nxos_udld_interface: *conf1
+      register: result
+
+    - assert: *false
+
+    - name: ensure interface has mode disabled
+      nxos_udld_interface: &conf3
+        interface: "{{ intname }}"
+        mode: disabled
+        state: present
+        provider: "{{ connection }}"
+      register: result
+
+    - assert: *true
+
+    - name: "Conf3 Idempotence"
+      nxos_udld_interface: *conf3
+      register: result
+
+    - assert: *false
 
-  - assert: *false
+    when: udld_enable
 
   - name: Remove the config
     nxos_udld_interface: &remove