Repository: ansible/ansible

Issue:
nxos_ntp 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_ntp

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, 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, BELOW THIS COMMENT, 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)I2(5)

##### 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 some issues with nxos_ntp module.

1. When only source_addr or source_int is changed, the module is sending key_id command also to the device even though nothing changed for that command so it is not idempotent.
2. There are many variables which cannot be set to default state individually unless we remove the entire config.
3. Integration test cases coverage is not complete.

Note: I will open a PR shortly to address the above issues
nxos_ntp 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_ntp

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, 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, BELOW THIS COMMENT, 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)I2(5)

##### 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 some issues with nxos_ntp module.

1. When only source_addr or source_int is changed, the module is sending key_id command also to the device even though nothing changed for that command so it is not idempotent.
2. There are many variables which cannot be set to default state individually unless we remove the entire config.
3. Integration test cases coverage is not complete.

Note: I will open a PR shortly to address the above issues


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_ntp.py b/lib/ansible/modules/network/nxos/nxos_ntp.py
--- a/lib/ansible/modules/network/nxos/nxos_ntp.py
+++ b/lib/ansible/modules/network/nxos/nxos_ntp.py
@@ -31,7 +31,7 @@
     key_id:
         description:
             - Authentication key identifier to use with
-              given NTP server or peer.
+              given NTP server or peer or keyword 'default'.
     prefer:
         description:
             - Makes given NTP server or peer the preferred
@@ -40,14 +40,16 @@
     vrf_name:
         description:
             - Makes the device communicate with the given
-              NTP server or peer over a specific VRF.
+              NTP server or peer over a specific VRF or
+              keyword 'default'.
     source_addr:
         description:
-            - Local source address from which NTP messages are sent.
+            - Local source address from which NTP messages are sent
+              or keyword 'default'
     source_int:
         description:
             - Local source interface from which NTP messages are sent.
-              Must be fully qualified interface name.
+              Must be fully qualified interface name or keyword 'default'
     state:
         description:
             - Manage the state of the resource.
@@ -171,10 +173,12 @@ def get_ntp_peer(module):
 
             split_ntp = ntp.splitlines()
             for peer_line in split_ntp:
+                if 'access-group' in peer_line:
+                    continue
                 ntp_peer = {}
                 try:
                     peer_address = None
-                    vrf_name = None
+                    vrf_name = 'default'
                     prefer = None
                     key_id = None
                     match_ntp = re.match(ntp_regex, peer_line, re.DOTALL)
@@ -246,11 +250,18 @@ def set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name):
 
 
 def config_ntp(delta, existing):
-    address = delta.get('address', existing.get('address'))
-    peer_type = delta.get('peer_type', existing.get('peer_type'))
-    vrf_name = delta.get('vrf_name', existing.get('vrf_name'))
-    key_id = delta.get('key_id', existing.get('key_id'))
-    prefer = delta.get('prefer', existing.get('prefer'))
+    if (delta.get('address') or delta.get('peer_type') or delta.get('vrf_name') or
+            delta.get('key_id') or delta.get('prefer')):
+        address = delta.get('address', existing.get('address'))
+        peer_type = delta.get('peer_type', existing.get('peer_type'))
+        key_id = delta.get('key_id', existing.get('key_id'))
+        prefer = delta.get('prefer', existing.get('prefer'))
+        vrf_name = delta.get('vrf_name', existing.get('vrf_name'))
+        if delta.get('key_id') == 'default':
+            key_id = None
+    else:
+        peer_type = None
+        prefer = None
 
     source_type = delta.get('source_type')
     source = delta.get('source')
@@ -266,6 +277,9 @@ def config_ntp(delta, existing):
 
     ntp_cmds = []
     if peer_type:
+        if existing.get('peer_type') and existing.get('address'):
+            ntp_cmds.append('no ntp {0} {1}'.format(existing.get('peer_type'),
+                                                    existing.get('address')))
         ntp_cmds.append(set_ntp_server_peer(
             peer_type, address, prefer, key_id, vrf_name))
     if source:
@@ -273,7 +287,11 @@ def config_ntp(delta, existing):
         existing_source = existing.get('source')
         if existing_source_type and source_type != existing_source_type:
             ntp_cmds.append('no ntp {0} {1}'.format(existing_source_type, existing_source))
-        ntp_cmds.append('ntp {0} {1}'.format(source_type, source))
+        if source == 'default':
+            if existing_source_type and existing_source:
+                ntp_cmds.append('no ntp {0} {1}'.format(existing_source_type, existing_source))
+        else:
+            ntp_cmds.append('ntp {0} {1}'.format(source_type, source))
 
     return ntp_cmds
 
@@ -309,6 +327,7 @@ def main():
     source_addr = module.params['source_addr']
     source_int = module.params['source_int']
     state = module.params['state']
+
     if source_int is not None:
         source_int = source_int.lower()
 
@@ -350,6 +369,9 @@ def main():
 
     if state == 'present':
         delta = dict(set(proposed.items()).difference(existing.items()))
+        if delta.get('key_id') and delta.get('key_id') == 'default':
+            if not existing.get('key_id'):
+                delta.pop('key_id')
         if delta:
             command = config_ntp(delta, existing)
             if command:


Test patch:
diff --git a/test/integration/targets/nxos_ntp/tests/common/sanity.yaml b/test/integration/targets/nxos_ntp/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_ntp/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_ntp/tests/common/sanity.yaml
@@ -39,6 +39,25 @@
       that:
         - "result.changed == false"
 
+  - name: Configure ntp with some defaults
+    nxos_ntp: &config1
+      peer: 1.2.3.4
+      key_id: default
+      prefer: enabled
+      vrf_name: default
+      source_addr: default
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: Idempotence Check
+    nxos_ntp: *config1
+    register: result
+
+  - assert: *false
+
   - name: Remove ntp config
     nxos_ntp: *remove
     register: result
@@ -51,6 +70,50 @@
 
   - assert: *false
 
+  - name: Configure ntp again
+    nxos_ntp: &config2
+      source_int: Ethernet1/3
+      peer: 1.2.3.4
+      prefer: enabled
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: Idempotence Check
+    nxos_ntp: *config2
+    register: result
+
+  - assert: *false
+
+  - name: Remove source interface
+    nxos_ntp: &config3
+      source_int: default
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: Idempotence Check
+    nxos_ntp: *config3
+    register: result
+
+  - assert: *false
+
+  - name: Remove ntp
+    nxos_ntp: *remove
+    register: result
+
+  - assert: *true
+
+  - name: Remove Idempotence Check
+    nxos_ntp: *remove
+    register: result
+
+  - assert: *false
+
   always:
   - name: Remove ntp config
     nxos_ntp: *remove