Repository: ansible/ansible

Issue:
nxos_udld 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.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
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
##### 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)I7(2)

##### SUMMARY
<!--- Explain the problem briefly -->

##### 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.
-->
There are multiple issues with nxos_udld module
* When the state is set to absent, both msg_time and aggressive are set to their default values but the documentation (and some error checking in the code) says only msg_time is set to default
* We cannot set msg_time parameter to 'default' as the state=absent makes aggressive also as default
* reset parameter can only take 'true' because it just calls reset on the 'reset' command on the udld. This is a one time operation with no config involved. There is no such thing as 'false' for this
* msg_time boundary checks are in the code which is not necessary as well as not advisable in case the platform changes the boundaries. 
* When state=absent, we do not need any other parameters so the 'require_one_of' should not be present in the code.

Note: I will open a PR shortly to address these above issues


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_udld.py b/lib/ansible/modules/network/nxos/nxos_udld.py
--- a/lib/ansible/modules/network/nxos/nxos_udld.py
+++ b/lib/ansible/modules/network/nxos/nxos_udld.py
@@ -34,8 +34,6 @@
     - Jason Edelman (@jedelman8)
 notes:
     - Tested against NXOSv 7.3.(0)D1(1) on VIRL
-    - When C(state=absent), it unconfigures existing settings C(msg_time) and set it
-      to its default value of 15.  It is cleaner to always use C(state=present).
     - Module will fail if the udld feature has not been previously enabled.
 options:
     aggressive:
@@ -46,18 +44,20 @@
         choices: ['enabled','disabled']
     msg_time:
         description:
-            - Message time in seconds for UDLD packets.
+            - Message time in seconds for UDLD packets or keyword 'default'.
         required: false
         default: null
     reset:
         description:
-            - Ability to reset UDLD down interfaces.
+            - Ability to reset all ports shut down by UDLD. 'state' parameter
+              cannot be 'absent' when this is present.
         required: false
         default: null
-        choices: ['true','false']
+        choices: ['true']
     state:
         description:
-            - Manage the state of the resource.
+            - Manage the state of the resource. When set to 'absent',
+              aggressive and msg_time are set to their default values.
         required: false
         default: present
         choices: ['present','absent']
@@ -117,6 +117,11 @@
 from ansible.module_utils.basic import AnsibleModule
 
 
+PARAM_TO_DEFAULT_KEYMAP = {
+    'msg_time': '15',
+}
+
+
 def execute_show_command(command, module, command_type='cli_show'):
     device_info = get_capabilities(module)
     network_api = device_info.get('network_api', 'nxapi')
@@ -156,42 +161,32 @@ def apply_key_map(key_map, table):
     return new_dict
 
 
-def get_commands_config_udld_global(delta, reset):
-    config_args = {
-        'enabled': 'udld aggressive',
-        'disabled': 'no udld aggressive',
-        'msg_time': 'udld message-time {msg_time}'
-    }
+def get_commands_config_udld_global(delta, reset, existing):
     commands = []
     for param, value in delta.items():
         if param == 'aggressive':
-            if value == 'enabled':
-                command = 'udld aggressive'
-            elif value == 'disabled':
-                command = 'no udld aggressive'
-        else:
-            command = config_args.get(param, 'DNE').format(**delta)
-        if command and command != 'DNE':
+            command = 'udld aggressive' if value == 'enabled' else 'no udld aggressive'
             commands.append(command)
-        command = None
-
+        elif param == 'msg_time':
+            if value == 'default':
+                if existing.get('msg_time') != PARAM_TO_DEFAULT_KEYMAP.get('msg_time'):
+                    commands.append('no udld message-time')
+            else:
+                commands.append('udld message-time ' + value)
     if reset:
         command = 'udld reset'
         commands.append(command)
     return commands
 
 
-def get_commands_remove_udld_global(delta):
-    config_args = {
-        'aggressive': 'no udld aggressive',
-        'msg_time': 'no udld message-time {msg_time}',
-    }
+def get_commands_remove_udld_global(existing):
     commands = []
-    for param, value in delta.items():
-        command = config_args.get(param, 'DNE').format(**delta)
-        if command and command != 'DNE':
-            commands.append(command)
-        command = None
+    if existing.get('aggressive') == 'enabled':
+        command = 'no udld aggressive'
+        commands.append(command)
+    if existing.get('msg_time') != PARAM_TO_DEFAULT_KEYMAP.get('msg_time'):
+        command = 'no udld message-time'
+        commands.append(command)
     return commands
 
 
@@ -222,7 +217,6 @@ def main():
     argument_spec.update(nxos_argument_spec)
 
     module = AnsibleModule(argument_spec=argument_spec,
-                           required_one_of=[['aggressive', 'msg_time', 'reset']],
                            supports_check_mode=True)
 
     warnings = list()
@@ -232,20 +226,8 @@ def main():
     reset = module.params['reset']
     state = module.params['state']
 
-    if (aggressive or reset) and state == 'absent':
-        module.fail_json(msg="It's better to use state=present when "
-                             "configuring or unconfiguring aggressive mode "
-                             "or using reset flag. state=absent is just for "
-                             "when using msg_time param.")
-
-    if msg_time:
-        try:
-            msg_time_int = int(msg_time)
-            if msg_time_int < 7 or msg_time_int > 90:
-                raise ValueError
-        except ValueError:
-            module.fail_json(msg='msg_time must be an integer'
-                                 'between 7 and 90')
+    if reset and state == 'absent':
+        module.fail_json(msg="state must be present when using reset flag.")
 
     args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset)
     proposed = dict((k, v) for k, v in args.items() if v is not None)
@@ -259,13 +241,12 @@ def main():
     commands = []
     if state == 'present':
         if delta:
-            command = get_commands_config_udld_global(dict(delta), reset)
+            command = get_commands_config_udld_global(dict(delta), reset, existing)
             commands.append(command)
 
     elif state == 'absent':
-        common = set(proposed.items()).intersection(existing.items())
-        if common:
-            command = get_commands_remove_udld_global(dict(common))
+        command = get_commands_remove_udld_global(existing)
+        if command:
             commands.append(command)
 
     cmds = flatten_list(commands)


Test patch:
diff --git a/test/integration/targets/nxos_udld/tests/common/sanity.yaml b/test/integration/targets/nxos_udld/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_udld/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_udld/tests/common/sanity.yaml
@@ -5,7 +5,7 @@
 
 - set_fact: udld_run="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
 
@@ -16,14 +16,9 @@
       state: enabled
       provider: "{{ connection }}"
 
-  - name: Reset udld
-    nxos_udld:
-      reset: True
-      provider: "{{ connection }}"
-
-  - name: Ensure udld agg mode is globally disabled and msg time is 20
+  - name: Configure udld
     nxos_udld: &conf1
-      aggressive: disabled
+      aggressive: enabled
       msg_time: 20
       provider: "{{ connection }}"
     register: result
@@ -32,7 +27,7 @@
       that:
         - "result.changed == true"
 
-  - name: "Conf1 Idempotence"
+  - name: "Check Idempotence"
     nxos_udld: *conf1
     register: result
 
@@ -40,22 +35,59 @@
       that:
         - "result.changed == false"
 
+  - name: Reset udld
+    nxos_udld:
+      reset: True
+      provider: "{{ connection }}"
 
-  - name: Ensure udld agg mode is globally enabled and msg time is 15
+  - name: Configure udld2
     nxos_udld: &conf2
-      aggressive: enabled
-      msg_time: 15
+      aggressive: disabled
       provider: "{{ connection }}"
     register: result
 
   - assert: *true
 
-  - name: "conf2 Idempotence"
+  - name: "Check Idempotence"
     nxos_udld: *conf2
     register: result
 
   - assert: *false
 
+  - name: Configure udld3
+    nxos_udld: &conf3
+      msg_time: default
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_udld: *conf3
+    register: result
+
+  - assert: *false
+
+  - name: Configure udld again
+    nxos_udld: *conf1
+    register: result
+
+  - assert: *true
+
+  - name: Remove udld config
+    nxos_udld: &conf4
+      state: absent
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_udld: *conf4
+    register: result
+
+  - assert: *false
+
   when: udld_run
 
   always: