Repository: ansible/ansible

Issue:
nxos_vlan 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_vlan

##### 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]
```

##### 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.
-->
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 few issues with nxos_vlan.
1.	Unable to set ‘name’ parameter
2.	‘mapped_vni’ doc says, it supports ‘default’ but there is support.
3.	‘interfaces’ should support ‘default’ keyword, otherwise, there is no way to remove all the interfaces assigned.
4. documentation to be corrected so that vlan_state is NOT deprecated ans state only accepts absent and present
5. ‘interfaces’ are working only if we create the vlan at the same time. If there is an existing vlan and change parameters for that vlan and also provide interfaces for that vlan in the same task, only interfaces are being changed, it skips all other changes.

Note: I will open a PR shortly to address the above issues

nxos_vlan 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_vlan

##### 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]
```

##### 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.
-->
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 few issues with nxos_vlan.
1.	Unable to set ‘name’ parameter
2.	‘mapped_vni’ doc says, it supports ‘default’ but there is support.
3.	‘interfaces’ should support ‘default’ keyword, otherwise, there is no way to remove all the interfaces assigned.
4. documentation to be corrected so that vlan_state is NOT deprecated ans state only accepts absent and present
5. ‘interfaces’ are working only if we create the vlan at the same time. If there is an existing vlan and change parameters for that vlan and also provide interfaces for that vlan in the same task, only interfaces are being changed, it skips all other changes.

Note: I will open a PR shortly to address the above issues



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_vlan.py b/lib/ansible/modules/network/nxos/nxos_vlan.py
--- a/lib/ansible/modules/network/nxos/nxos_vlan.py
+++ b/lib/ansible/modules/network/nxos/nxos_vlan.py
@@ -38,10 +38,10 @@
       - Range of VLANs such as 2-10 or 2,5,10-15, etc.
   name:
     description:
-      - Name of VLAN.
+      - Name of VLAN or keyword 'default'.
   interfaces:
     description:
-      - List of interfaces that should be associated to the VLAN.
+      - List of interfaces that should be associated to the VLAN or keyword 'default'.
     version_added: "2.5"
   associated_interfaces:
     description:
@@ -52,7 +52,6 @@
   vlan_state:
     description:
       - Manage the vlan operational state of the VLAN
-        This is being deprecated in favor of state.
     default: active
     choices: ['active','suspend']
   admin_state:
@@ -69,9 +68,8 @@
   state:
     description:
       - Manage the state of the resource.
-        Active and Suspend will assume the vlan is present.
     default: present
-    choices: ['present','absent', 'active', 'suspend']
+    choices: ['present','absent']
   mode:
     description:
       - Set VLAN mode to classical ethernet or fabricpath.
@@ -162,19 +160,28 @@ def search_obj_in_list(vlan_id, lst):
 
 def get_diff(w, obj):
     c = deepcopy(w)
-    entries = ('interfaces', 'associated_interfaces', 'name', 'delay', 'vlan_range')
+    entries = ('interfaces', 'associated_interfaces', 'delay', 'vlan_range')
     for key in entries:
         if key in c:
             del c[key]
 
     o = deepcopy(obj)
     del o['interfaces']
-    del o['name']
     if o['vlan_id'] == w['vlan_id']:
         diff_dict = dict(set(c.items()) - set(o.items()))
         return diff_dict
 
 
+def is_default_name(obj, vlan_id):
+    cname = obj['name']
+    if ('VLAN' in cname):
+        vid = int(cname[4:])
+        if vid == int(vlan_id):
+            return True
+
+    return False
+
+
 def map_obj_to_commands(updates, module, os_platform):
     commands = list()
     want, have = updates
@@ -208,13 +215,13 @@ def map_obj_to_commands(updates, module, os_platform):
             if not obj_in_have:
                 commands.append('vlan {0}'.format(vlan_id))
 
-                if name:
+                if name and name != 'default':
                     commands.append('name {0}'.format(name))
                 if mode:
                     commands.append('mode {0}'.format(mode))
                 if vlan_state:
                     commands.append('state {0}'.format(vlan_state))
-                if mapped_vni != 'None':
+                if mapped_vni != 'None' and mapped_vni != 'default':
                     commands.append('vn-segment {0}'.format(mapped_vni))
                 if admin_state == 'up':
                     commands.append('no shutdown')
@@ -222,7 +229,7 @@ def map_obj_to_commands(updates, module, os_platform):
                     commands.append('shutdown')
                 commands.append('exit')
 
-                if interfaces:
+                if interfaces and interfaces[0] != 'default':
                     for i in interfaces:
                         commands.append('interface {0}'.format(i))
                         commands.append('switchport')
@@ -230,7 +237,38 @@ def map_obj_to_commands(updates, module, os_platform):
                         commands.append('switchport access vlan {0}'.format(vlan_id))
 
             else:
-                if interfaces:
+                diff = get_diff(w, obj_in_have)
+                if diff:
+                    commands.append('vlan {0}'.format(vlan_id))
+                    for key, value in diff.items():
+                        if key == 'name':
+                            if name != 'default':
+                                if name is not None:
+                                    commands.append('name {0}'.format(value))
+                            else:
+                                if not is_default_name(obj_in_have, vlan_id):
+                                    commands.append('no name')
+                        if key == 'vlan_state':
+                            commands.append('state {0}'.format(value))
+                        if key == 'mapped_vni':
+                            if value == 'default':
+                                if obj_in_have['mapped_vni'] != 'None':
+                                    commands.append('no vn-segment')
+                            elif value != 'None':
+                                commands.append('vn-segment {0}'.format(value))
+                        if key == 'admin_state':
+                            if value == 'up':
+                                commands.append('no shutdown')
+                            elif value == 'down':
+                                commands.append('shutdown')
+                        if key == 'mode':
+                            commands.append('mode {0}'.format(value))
+                    if len(commands) > 1:
+                        commands.append('exit')
+                    else:
+                        del commands[:]
+
+                if interfaces and interfaces[0] != 'default':
                     if not obj_in_have['interfaces']:
                         for i in interfaces:
                             commands.append('vlan {0}'.format(vlan_id))
@@ -259,24 +297,15 @@ def map_obj_to_commands(updates, module, os_platform):
                             commands.append('switchport mode access')
                             commands.append('no switchport access vlan {0}'.format(vlan_id))
 
-                else:
-                    diff = get_diff(w, obj_in_have)
-                    if diff:
-                        commands.append('vlan {0}'.format(vlan_id))
-                        for key, value in diff.items():
-                            if key == 'vlan_state':
-                                commands.append('state {0}'.format(value))
-                            if key == 'mapped_vni':
-                                if value != 'None':
-                                    commands.append('vn-segment {0}'.format(value))
-                            if key == 'admin_state':
-                                if value == 'up':
-                                    commands.append('no shutdown')
-                                elif value == 'down':
-                                    commands.append('shutdown')
-                            if key == 'mode':
-                                commands.append('mode {0}'.format(value))
-                        commands.append('exit')
+                elif interfaces and interfaces[0] == 'default':
+                    if obj_in_have['interfaces']:
+                        for i in obj_in_have['interfaces']:
+                            commands.append('vlan {0}'.format(vlan_id))
+                            commands.append('exit')
+                            commands.append('interface {0}'.format(i))
+                            commands.append('switchport')
+                            commands.append('switchport mode access')
+                            commands.append('no switchport access vlan {0}'.format(vlan_id))
 
     return commands
 
@@ -493,9 +522,9 @@ def main():
         interfaces=dict(type='list'),
         associated_interfaces=dict(type='list'),
         vlan_state=dict(choices=['active', 'suspend'], required=False, default='active'),
-        mapped_vni=dict(required=False, type='int'),
+        mapped_vni=dict(required=False),
         delay=dict(default=10, type='int'),
-        state=dict(choices=['present', 'absent', 'active', 'suspend'], default='present', required=False),
+        state=dict(choices=['present', 'absent'], default='present', required=False),
         admin_state=dict(choices=['up', 'down'], required=False, default='up'),
         mode=dict(choices=['ce', 'fabricpath'], required=False, default='ce'),
     )


Test patch:
diff --git a/test/integration/targets/nxos_vlan/tests/common/interface.yaml b/test/integration/targets/nxos_vlan/tests/common/interface.yaml
--- a/test/integration/targets/nxos_vlan/tests/common/interface.yaml
+++ b/test/integration/targets/nxos_vlan/tests/common/interface.yaml
@@ -7,6 +7,7 @@
     lines:
       - no vlan 100
     provider: "{{ connection }}"
+  ignore_errors: yes
 
 - name: setup - remove vlan from interfaces used in test(part1)
   nxos_config:
diff --git a/test/integration/targets/nxos_vlan/tests/common/sanity.yaml b/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
@@ -3,6 +3,9 @@
 - debug: msg="Using provider={{ connection.transport }}"
   when: ansible_connection == "local"
 
+- set_fact: testint1="{{ nxos_int1 }}"
+- set_fact: testint2="{{ nxos_int2 }}"
+
 - block:
   - name: "Enable feature vn segment"
     nxos_config: 
@@ -44,7 +47,7 @@
   - assert: *true
     when: platform is search('N9K')
 
-  - name: "web Idempotence"
+  - name: "web1 Idempotence"
     nxos_vlan: *web1
     register: result
     when: platform is search('N9K')
@@ -52,8 +55,30 @@
   - assert: *false
     when: platform is search('N9K')
 
-  - name: Ensure VLAN 50 exists with the name WEB and is in the shutdown state
+  - name: change name and vni to default
     nxos_vlan: &web2
+      vlan_id: 50
+      vlan_state: active
+      admin_state: up
+      name: default
+      mapped_vni: default
+      provider: "{{ connection }}"
+    register: result
+    when: platform is search('N9K')
+
+  - assert: *true
+    when: platform is search('N9K')
+
+  - name: "web2 Idempotence"
+    nxos_vlan: *web2
+    register: result
+    when: platform is search('N9K')
+
+  - assert: *false
+    when: platform is search('N9K')
+
+  - name: Ensure VLAN 50 exists with the name WEB and is in the shutdown state
+    nxos_vlan: &web3
       vlan_id: 50
       vlan_state: suspend
       admin_state: down
@@ -65,14 +90,74 @@
   - assert: *true
     when: platform is search('N3K|N7K')
 
-  - name: "web Idempotence"
-    nxos_vlan: *web2
+  - name: "web3 Idempotence"
+    nxos_vlan: *web3
     register: result
     when: platform is search('N3K|N7K')
 
   - assert: *false
     when: platform is search('N3K|N7K')
 
+  - name: Change name to default
+    nxos_vlan: &web4
+      vlan_id: 50
+      vlan_state: active
+      admin_state: up
+      name: default
+      provider: "{{ connection }}"
+    register: result
+    when: platform is search('N3K|N7K')
+
+  - assert: *true
+    when: platform is search('N3K|N7K')
+
+  - name: "web4 Idempotence"
+    nxos_vlan: *web4
+    register: result
+    when: platform is search('N3K|N7K')
+
+  - assert: *false
+    when: platform is search('N3K|N7K')
+
+# Uncomment this once the get_capabilities() work on nxapi as well
+#  - name: Change mode
+#    nxos_vlan: &mode1
+#      vlan_id: 50
+#      mode: fabricpath
+#      provider: "{{ connection }}"
+#    register: result
+#    when: platform is search('N5k|N7K')
+#
+#  - assert: *true
+#    when: platform is search('N5k|N7K')
+#
+#  - name: "mode1 Idempotence"
+#    nxos_vlan: *mode1
+#    register: result
+#    when: platform is search('N5k|N7K')
+#
+#  - assert: *false
+#    when: platform is search('N5k|N7K')
+#
+#  - name: Change mode again
+#    nxos_vlan: &mode2
+#      vlan_id: 50
+#      mode: ce
+#      provider: "{{ connection }}"
+#    register: result
+#    when: platform is search('N5k|N7K')
+#
+#  - assert: *true
+#    when: platform is search('N5k|N7K')
+#
+#  - name: "mode2 Idempotence"
+#    nxos_vlan: *mode2
+#    register: result
+#    when: platform is search('N5k|N7K')
+#
+#  - assert: *false
+#    when: platform is search('N5k|N7K')
+
   - name: Ensure VLAN is NOT on the device
     nxos_vlan: &no_vlan
       vlan_id: 50
@@ -88,7 +173,44 @@
 
   - assert: *false
 
+  - name: Add interfaces to vlan
+    nxos_vlan: &addint
+      vlan_id: 101
+      vlan_state: suspend
+      interfaces:
+        - "{{ testint1 }}"
+        - "{{ testint2 }}"
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Addint idempotence"
+    nxos_vlan: *addint
+    register: result
+
+  - assert: *false
+
+  - name: Remove interfaces from vlan
+    nxos_vlan: &remint
+      vlan_id: 101
+      interfaces: default
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Remint idempotence"
+    nxos_vlan: *remint
+    register: result
+
+  - assert: *false
+
   always:
+    - name: Remove int from vlan
+      nxos_vlan: *remint
+      ignore_errors: yes
+
     - name: remove vlans
       nxos_vlan:
         vlan_range: "2-10,20,50,55-60,100-150"