Repository: ansible/ansible

Issue:
nxos_acl 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_acl
##### 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)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 MAJOR issues with nxos_acl module

This module treats some parameters as core options and the rest as optional options. 
1. If core params have changed for an existing ace, then, new core params are set but all the optional params are completely removed.
2. If some core params are not present in the current playbook and they do exist in the existing configuration, they are left out and there is no way of removing them other than deleting the config completely
3. Similar to the issue 2 above, the same problem exists for optional params as well
4. remark parameter cannot be changed at all
5. If protocol is given as a number, it is NOT idempotent

Note: I will open a PR shortly to address the above issues



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_acl.py b/lib/ansible/modules/network/nxos/nxos_acl.py
--- a/lib/ansible/modules/network/nxos/nxos_acl.py
+++ b/lib/ansible/modules/network/nxos/nxos_acl.py
@@ -203,6 +203,7 @@ def get_acl(module, acl_name, seq_number):
         for acl in all_acl_body:
             if acl.get('acl_name') == acl_name:
                 acl_body = acl
+                break
 
     try:
         acl_entries = acl_body['TABLE_seqno']['ROW_seqno']
@@ -226,7 +227,7 @@ def get_acl(module, acl_name, seq_number):
             temp['action'] = 'remark'
         else:
             temp['action'] = each.get('permitdeny')
-            temp['proto'] = each.get('proto', each.get('proto_str', each.get('ip')))
+            temp['proto'] = str(each.get('proto', each.get('proto_str', each.get('ip'))))
             temp['src'] = each.get('src_any', each.get('src_ip_prefix'))
             temp['src_port_op'] = each.get('src_port_op')
             temp['src_port1'] = each.get('src_port1_num')
@@ -458,13 +459,35 @@ def main():
     delta_options = {}
 
     if not existing_core.get('remark'):
-        delta_core = dict(
+        dcore = dict(
             set(proposed_core.items()).difference(
                 existing_core.items())
         )
-        delta_options = dict(
-            set(proposed_options.items()).difference(
-                existing_options.items())
+        if not dcore:
+            # check the diff in the other way just in case
+            dcore = dict(
+                set(existing_core.items()).difference(
+                    proposed_core.items())
+            )
+        delta_core = dcore
+        if delta_core:
+            delta_options = proposed_options
+        else:
+            doptions = dict(
+                set(proposed_options.items()).difference(
+                    existing_options.items())
+            )
+            # check the diff in the other way just in case
+            if not doptions:
+                doptions = dict(
+                    set(existing_options.items()).difference(
+                        proposed_options.items())
+                )
+            delta_options = doptions
+    else:
+        delta_core = dict(
+            set(proposed_core.items()).difference(
+                existing_core.items())
         )
 
     if state == 'present':


Test patch:
diff --git a/test/integration/targets/nxos_acl/tests/common/sanity.yaml b/test/integration/targets/nxos_acl/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_acl/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_acl/tests/common/sanity.yaml
@@ -10,12 +10,12 @@
   nxos_acl: &remove
     name: TEST_ACL
     seq: 10
-    state: absent
+    state: delete_acl
     provider: "{{ connection }}"
   ignore_errors: yes
 
-- name: "Configure ACL"
-  nxos_acl: &configure
+- name: "Configure ACE10"
+  nxos_acl: &conf10
     name: TEST_ACL
     seq: 10
     action: permit
@@ -27,6 +27,8 @@
     ack: 'enable'
     dscp: 'af43'
     dest: any
+    dest_port_op: neq
+    dest_port1: 1899
     urg: 'enable'
     psh: 'enable'
     established: 'enable'
@@ -44,13 +46,187 @@
       - "result.changed == true"
 
 - name: "Check Idempotence"
-  nxos_acl: *configure
+  nxos_acl: *conf10
   register: result
 
 - assert: &false
     that:
       - "result.changed == false"
 
+- name: "Change ACE10"
+  nxos_acl: &chg10
+    name: TEST_ACL
+    seq: 10
+    action: deny
+    proto: tcp
+    src: 1.1.1.1/24
+    src_port_op: range
+    src_port1: 1900
+    src_port2: 1910
+    ack: 'enable'
+    dscp: 'af43'
+    dest: any
+    dest_port_op: neq
+    dest_port1: 1899
+    urg: 'enable'
+    psh: 'enable'
+    established: 'enable'
+    log: 'enable'
+    fin: 'enable'
+    rst: 'enable'
+    syn: 'enable'
+    time_range: "{{time_range|default(omit)}}"
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *chg10
+  register: result
+
+- assert: *false
+
+- name: "ace remark"
+  nxos_acl: &remark
+    name: TEST_ACL
+    seq: 20
+    action: remark
+    remark: test_remark
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *remark
+  register: result
+
+- assert: *false
+
+- name: "change remark"
+  nxos_acl: &chgremark
+    name: TEST_ACL
+    seq: 20
+    action: remark
+    remark: changed_remark
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *chgremark
+  register: result
+
+- assert: *false
+
+- name: "ace 30"
+  nxos_acl: &ace30
+    name: TEST_ACL
+    seq: 30
+    action: deny
+    proto: 24
+    src: any
+    dest: any
+    fragments: enable
+    precedence: network
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *ace30
+  register: result
+
+- assert: *false
+
+- name: "change ace 30 options"
+  nxos_acl: &chgace30opt
+    name: TEST_ACL
+    seq: 30
+    action: deny
+    proto: 24
+    src: any
+    dest: any
+    precedence: network
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *chgace30opt
+  register: result
+
+- assert: *false
+
+- name: "ace 40"
+  nxos_acl: &ace40
+    name: TEST_ACL
+    seq: 40
+    action: permit
+    proto: udp
+    src: any
+    src_port_op: neq
+    src_port1: 1200
+    dest: any
+    precedence: network
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *ace40
+  register: result
+
+- assert: *false
+
+- name: "change ace 40"
+  nxos_acl: &chgace40
+    name: TEST_ACL
+    seq: 40
+    action: permit
+    proto: udp
+    src: any
+    dest: any
+    precedence: network
+    state: present
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *chgace40
+  register: result
+
+- assert: *false
+
+- name: "remove ace 30"
+  nxos_acl: &remace30
+    name: TEST_ACL
+    seq: 30
+    state: absent
+    provider: "{{ connection }}"
+  register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+  nxos_acl: *remace30
+  register: result
+
+- assert: *false
+
 - name: "Remove ACL"
   nxos_acl: *remove
   register: result