Repository: ansible/ansible

Issue:
(P1) nxos_udld_interface fails for certain interfaces for mode = enable
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
nxos_udld_interface

##### ANSIBLE VERSION
```
ansible 2.4.0 (devel 54cd641df4) last updated 2017/07/19 18:06:44 (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/p2/Projects/nxos_ansible/p1_ansible/lib/ansible
  executable location = /root/p2/Projects/nxos_ansible/p1_ansible/bin/ansible
  python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]
```

##### OS / ENVIRONMENT
Ansible Server : Ubuntu 14.04
Device: NX-OSv9K running 7.0(3)I2(5)

##### SUMMARY
- when the mode = enable, nxos_udld_interface tries to determine whether the interface is copper or fiber because the underlying commands are different for each type. However, the type cannot be determined exactly by using "show int status" as is being done now. The right command to use is "show interface <interface> transceiver". This shows the type which in theory can be mapped to copper vs fiber but after talking to number of developers, it is found that new transceivers keep getting added all the time and there is no easy way to maintain a matrix for this. 
For ex: In the current code, it is checking if '1000' exists in the type and making it copper. But on one of the devices the output of "show int status" gives this:
==
Eth1/1           --              disabled  routed    auto    auto    1000base-SX
==
Here this interface is fiber but the code is assuming it as copper because it has '1000' in the type. 
Since there is no 100% fool proof way to determine whether an interface is copper or fiber, it is better to change code to run one set of commands and catch any errors and try the other set, just to be safe. 

##### STEPS TO REPRODUCE
```
- name: ensure interface has mode enabled
    nxos_udld_interface: &conf2
      interface: ethernet1/1
      mode: enabled
      state: present
      provider: "{{ cli }}"
```

##### EXPECTED RESULTS
The commands should be executed and mode should be set to enable always.

##### ACTUAL RESULTS
It fails if the type cannot be determined properly with simple string checks.



Discussion:
cc @GGabriele @jedelman8 @mikewiebe @privateip @rahushen @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/module_utils/nxos.py b/lib/ansible/module_utils/nxos.py
--- a/lib/ansible/module_utils/nxos.py
+++ b/lib/ansible/module_utils/nxos.py
@@ -162,7 +162,7 @@ def run_commands(self, commands, check_rc=True):
             responses.append(out)
         return responses
 
-    def load_config(self, config):
+    def load_config(self, config, return_error=False):
         """Sends configuration commands to the remote device
         """
 
@@ -241,7 +241,7 @@ def _request_builder(self, commands, output, version='1.0', chunk='0', sid=None)
 
         return dict(ins_api=msg)
 
-    def send_request(self, commands, output='text', check_status=True):
+    def send_request(self, commands, output='text', check_status=True, return_error=False):
         # only 10 show commands can be encoded in each request
         # messages sent to the remote device
         if output != 'config':
@@ -292,7 +292,10 @@ def send_request(self, commands, output='text', check_status=True):
                 output = response['ins_api']['outputs']['output']
                 for item in to_list(output):
                     if check_status and item['code'] != '200':
-                        self._error(output=output, **item)
+                        if return_error:
+                            result.append(item)
+                        else:
+                            self._error(output=output, **item)
                     elif 'body' in item:
                         result.append(item['body'])
                     # else:
@@ -344,12 +347,15 @@ def _send(commands, output):
 
         return responses
 
-    def load_config(self, commands):
+    def load_config(self, commands, return_error=False):
         """Sends the ordered set of commands to the device
         """
         commands = to_list(commands)
-        self.send_request(commands, output='config')
-        return []
+        msg = self.send_request(commands, output='config', check_status=True, return_error=return_error)
+        if return_error:
+            return msg
+        else:
+            return []
 
 
 def is_json(cmd):
@@ -398,6 +404,6 @@ def run_commands(module, commands, check_rc=True):
     return conn.run_commands(to_command(module, commands), check_rc)
 
 
-def load_config(module, config):
+def load_config(module, config, return_error=False):
     conn = get_connection(module)
-    return conn.load_config(config)
+    return conn.load_config(config, return_error=return_error)
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
@@ -115,10 +115,6 @@
 from ansible.module_utils.basic import AnsibleModule
 
 
-import re
-import re
-
-
 def execute_show_command(command, module, command_type='cli_show'):
     if module.params['transport'] == 'cli':
         if 'show run' not in command:
@@ -167,50 +163,51 @@ def get_udld_interface(module, interface):
     return interface_udld
 
 
-def is_interface_copper(module, interface):
-    command = 'show interface status'
-    copper = []
-    try:
-        body = execute_show_command(command, module)[0]
-        table = body['TABLE_interface']['ROW_interface']
-        for each in table:
-            itype = each.get('type', 'DNE')
-            if 'CU' in itype or '1000' in itype or '10GBaseT' in itype:
-                copper.append(str(each['interface'].lower()))
-    except (KeyError, AttributeError):
-        pass
-
-    if interface in copper:
-        found = True
-    else:
-        found = False
+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))
 
-    return found
+    return commands
 
 
-def get_commands_config_udld_interface(delta, interface, module, existing):
+def get_commands_config_udld_interface2(delta, interface, module, existing):
     commands = []
-    copper = is_interface_copper(module, interface)
     if delta:
         mode = delta['mode']
         if mode == 'aggressive':
             command = 'udld aggressive'
-        elif copper:
-            if mode == 'enabled':
-                if existing['mode'] == 'aggressive':
-                    command = 'no udld aggressive ; udld enable'
-                else:
-                    command = 'udld enable'
-            elif mode == 'disabled':
-                command = 'no udld enable'
-        elif not copper:
-            if mode == 'enabled':
-                if existing['mode'] == 'aggressive':
-                    command = 'no udld aggressive ; no udld disable'
-                else:
-                    command = 'no udld disable'
-            elif mode == 'disabled':
-                command = 'udld disable'
+        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':
+            command = 'udld enable'
     if command:
         commands.append(command)
         commands.insert(0, 'interface {0}'.format(interface))
@@ -218,24 +215,17 @@ def get_commands_config_udld_interface(delta, interface, module, existing):
     return commands
 
 
-def get_commands_remove_udld_interface(delta, interface, module, existing):
+def get_commands_remove_udld_interface2(delta, interface, module, existing):
     commands = []
-    copper = is_interface_copper(module, interface)
 
     if delta:
         mode = delta['mode']
         if mode == 'aggressive':
             command = 'no udld aggressive'
-        elif copper:
-            if mode == 'enabled':
-                command = 'no udld enable'
-            elif mode == 'disabled':
-                command = 'udld enable'
-        elif not copper:
-            if mode == 'enabled':
-                command = 'udld disable'
-            elif mode == 'disabled':
-                command = 'no udld disable'
+        if mode == 'enabled':
+            command = 'udld disable'
+        elif mode == 'disabled':
+            command = 'no udld disable'
     if command:
         commands.append(command)
         commands.insert(0, 'interface {0}'.format(interface))
@@ -259,7 +249,6 @@ def main():
     warnings = list()
     check_args(module, warnings)
 
-
     interface = module.params['interface'].lower()
     mode = module.params['mode']
     state = module.params['state']
@@ -274,13 +263,13 @@ def main():
     commands = []
     if state == 'present':
         if delta:
-            command = get_commands_config_udld_interface(delta, interface,
+            command = get_commands_config_udld_interface1(delta, interface,
                                                          module, existing)
             commands.append(command)
     elif state == 'absent':
         common = set(proposed.items()).intersection(existing.items())
         if common:
-            command = get_commands_remove_udld_interface(
+            command = get_commands_remove_udld_interface1(
                 dict(common), interface, module, existing
                 )
             commands.append(command)
@@ -291,7 +280,32 @@ def main():
             module.exit_json(changed=True, commands=cmds)
         else:
             changed = True
-            load_config(module, cmds)
+            # 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)
@@ -306,6 +320,6 @@ def main():
 
     module.exit_json(**results)
 
+
 if __name__ == '__main__':
     main()
-


Test patch: