Repository: ansible/ansible

Issue:
eos_vlan and eos_vrf modules not idempotent if interface parameter is given as input
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master 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
-->
eos_vlan
eos_vrf

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
2.4
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### 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.
-->

##### 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.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Manage interface
  eos_vlan:
    vlan_id: 30
    name: vlan:30
    interfaces:
    - Ethernet 2
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Ansible run should be successful

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Fails with error
<!--- Paste verbatim command output between quotes below -->
```
fatal: [veos01]: FAILED! => {
	<—snip—>
    },
    "msg": "Interface Ethernet 2 not configured on vlan 30"
}

```



Discussion:
Files identified in the description:
* [lib/ansible/modules/network/eos/eos_vlan.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/eos/eos_vlan.py)
* [lib/ansible/modules/network/eos/eos_vrf.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/eos/eos_vrf.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @privateip @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/modules/network/eos/eos_interface.py b/lib/ansible/modules/network/eos/eos_interface.py
--- a/lib/ansible/modules/network/eos/eos_interface.py
+++ b/lib/ansible/modules/network/eos/eos_interface.py
@@ -27,7 +27,8 @@
 options:
   name:
     description:
-      - Name of the Interface to be configured on remote device.
+      - Name of the Interface to be configured on remote device. The name of interface
+        should be in expanded format and not abbreviated.
     required: true
   description:
     description:
diff --git a/lib/ansible/modules/network/eos/eos_vlan.py b/lib/ansible/modules/network/eos/eos_vlan.py
--- a/lib/ansible/modules/network/eos/eos_vlan.py
+++ b/lib/ansible/modules/network/eos/eos_vlan.py
@@ -45,7 +45,8 @@
     required: true
   interfaces:
     description:
-      - List of interfaces that should be associated to the VLAN.
+      - List of interfaces that should be associated to the VLAN. The name of interface
+        should be in expanded format and not abbreviated.
   delay:
     description:
       - Delay the play should wait to check for declarative intent params values.
@@ -214,7 +215,7 @@ def map_config_to_obj(module):
         if len(splitted_line) > 3:
 
             for i in splitted_line[3].split(','):
-                obj['interfaces'].append(i.strip().replace('Et', 'Ethernet'))
+                obj['interfaces'].append(i.strip().replace('Et', 'ethernet'))
 
         objs.append(obj)
 
@@ -230,6 +231,9 @@ def map_params_to_obj(module):
                 if item.get(key) is None:
                     item[key] = module.params[key]
 
+            if item.get('interfaces'):
+                item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf]
+
             d = item.copy()
             d['vlan_id'] = str(d['vlan_id'])
 
@@ -239,7 +243,7 @@ def map_params_to_obj(module):
             'vlan_id': str(module.params['vlan_id']),
             'name': module.params['name'],
             'state': module.params['state'],
-            'interfaces': module.params['interfaces']
+            'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else []
         })
 
     return obj
@@ -318,5 +322,6 @@ def main():
 
     module.exit_json(**result)
 
+
 if __name__ == '__main__':
     main()
diff --git a/lib/ansible/modules/network/eos/eos_vrf.py b/lib/ansible/modules/network/eos/eos_vrf.py
--- a/lib/ansible/modules/network/eos/eos_vrf.py
+++ b/lib/ansible/modules/network/eos/eos_vrf.py
@@ -45,8 +45,10 @@
       - Route distinguisher of the VRF
   interfaces:
     description:
-      - List of interfaces to check the VRF has been
-        configured correctly.
+      - Identifies the set of interfaces that
+        should be configured in the VRF. Interfaces must be routed
+        interfaces in order to be placed into a VRF. The name of interface
+        should be in expanded format and not abbreviated.
   aggregate:
     description: List of VRFs definitions
   purge:
@@ -195,13 +197,13 @@ def map_config_to_obj(module):
             obj = {}
             obj['name'] = splitted_line[0]
             obj['rd'] = splitted_line[1]
-            obj['interfaces'] = None
+            obj['interfaces'] = []
 
             if len(splitted_line) > 4:
                 obj['interfaces'] = []
 
                 for i in splitted_line[4].split(','):
-                    obj['interfaces'].append(i.strip())
+                    obj['interfaces'].append(i.strip().lower())
 
             objs.append(obj)
 
@@ -216,13 +218,17 @@ def map_params_to_obj(module):
             for key in item:
                 if item.get(key) is None:
                     item[key] = module.params[key]
+
+            if item.get('interfaces'):
+                item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf]
+
             obj.append(item.copy())
     else:
         obj.append({
             'name': module.params['name'],
             'state': module.params['state'],
             'rd': module.params['rd'],
-            'interfaces': module.params['interfaces']
+            'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else []
         })
 
     return obj


Test patch:
diff --git a/test/integration/targets/eos_vlan/tests/cli/basic.yaml b/test/integration/targets/eos_vlan/tests/cli/basic.yaml
--- a/test/integration/targets/eos_vlan/tests/cli/basic.yaml
+++ b/test/integration/targets/eos_vlan/tests/cli/basic.yaml
@@ -134,9 +134,9 @@
     that:
       - "result.changed == true"
       - "'vlan 4000' in result.commands"
-      - "'interface Ethernet1' in result.commands"
+      - "'interface ethernet1' in result.commands"
       - "'switchport access vlan 4000' in result.commands"
-      - "'interface Ethernet2' in result.commands"
+      - "'interface ethernet2' in result.commands"
       - "'switchport access vlan 4000' in result.commands"
       # Ensure sessions contains epoc. Will fail after 18th May 2033
       - "'ansible_1' in result.session_name"
@@ -146,8 +146,8 @@
     vlan_id: 4000
     state: present
     interfaces:
-      - Ethernet1
-      - Ethernet2
+      - ethernet 1   # interface name modified to test case insensitive and space scenario
+      - ethernet 2   # interface name modified to test case insensitive and space scenario
     authorize: yes
     provider: "{{ cli }}"
   become: yes
@@ -175,7 +175,7 @@
     that:
       - "result.changed == true"
       - "'vlan 4000' in result.commands"
-      - "'interface Ethernet2' in result.commands"
+      - "'interface ethernet2' in result.commands"
       - "'no switchport access vlan 4000' in result.commands"
       # Ensure sessions contains epoc. Will fail after 18th May 2033
       - "'ansible_1' in result.session_name"
@@ -185,7 +185,7 @@
     vlan_id: 4000
     state: present
     interfaces:
-      - Ethernet1
+      - ethernet 1 # interface name modified to handle case insensitive and space scenario
     authorize: yes
     provider: "{{ cli }}"
   become: yes
diff --git a/test/integration/targets/eos_vrf/tests/cli/basic.yaml b/test/integration/targets/eos_vrf/tests/cli/basic.yaml
--- a/test/integration/targets/eos_vrf/tests/cli/basic.yaml
+++ b/test/integration/targets/eos_vrf/tests/cli/basic.yaml
@@ -109,7 +109,7 @@
 - assert:
     that:
       - "result.changed == true"
-      - "'interface Ethernet2' in result.commands"
+      - "'interface ethernet2' in result.commands"
       - "'vrf forwarding test' in result.commands"
       # Ensure sessions contains epoc. Will fail after 18th May 2033
       - "'ansible_1' in result.session_name"
@@ -121,7 +121,7 @@
     state: present
     authorize: yes
     interfaces:
-      - Ethernet2
+      - ethernet 2  # interface name modified to test case insensitive and space scenario
     provider: "{{ cli }}"
   become: yes
   register: result