Repository: ansible/ansible

Issue:
nxos_static_route 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_static_route
##### 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
There are few issues with nxos_static_route.
* There is no 'default' keyword support for optional parameters
* For removing a static route, we MUST specify all the optional parameters as well because of certain checks in the code and this is not clean because they are optional and not really needed.
* If the same static route exists on default and non-default vrfs, there is a potential for set (and idempotent) failures.

Note: I will open a PR shortly to address the above issues
nxos_static_route 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_static_route
##### 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
There are few issues with nxos_static_route.
* There is no 'default' keyword support for optional parameters
* For removing a static route, we MUST specify all the optional parameters as well because of certain checks in the code and this is not clean because they are optional and not really needed.
* If the same static route exists on default and non-default vrfs, there is a potential for set (and idempotent) failures.

Note: I will open a PR shortly to address the above issues


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_static_route.py b/lib/ansible/modules/network/nxos/nxos_static_route.py
--- a/lib/ansible/modules/network/nxos/nxos_static_route.py
+++ b/lib/ansible/modules/network/nxos/nxos_static_route.py
@@ -52,13 +52,13 @@
     default: default
   tag:
     description:
-      - Route tag value (numeric).
+      - Route tag value (numeric) or keyword 'default'.
   route_name:
     description:
-      - Name of the route. Used with the name parameter on the CLI.
+      - Name of the route or keyword 'default'. Used with the name parameter on the CLI.
   pref:
     description:
-      - Preference or administrative difference of route (range 1-255).
+      - Preference or administrative difference of route (range 1-255) or keyword 'default'.
     aliases:
       - admin_distance
   aggregate:
@@ -67,8 +67,8 @@
   state:
     description:
       - Manage the state of the resource.
-    required: true
     choices: ['present','absent']
+    default: 'present'
 '''
 
 EXAMPLES = '''
@@ -105,66 +105,41 @@ def reconcile_candidate(module, candidate, prefix, w):
 
     parents = []
     commands = []
+    yrc = remove_command.replace('no ', '')
     if w['vrf'] == 'default':
-        config = netcfg.get_section(set_command)
-        if config and state == 'absent':
+        netcfg = str(netcfg).split('\n')
+        ncfg = []
+        for line in netcfg:
+            # remove ip route commands of non-default vrfs from
+            # the running config just in case the same commands
+            # exist in default and non-default vrfs
+            if '  ip route' not in line:
+                ncfg.append(line)
+        if any(yrc in s for s in ncfg) and state == 'absent':
             commands = [remove_command]
-        elif not config and state == 'present':
-            commands = [set_command]
+        elif set_command not in ncfg and state == 'present':
+            if any(yrc in s for s in ncfg):
+                commands = [remove_command, set_command]
+            else:
+                commands = [set_command]
     else:
         parents = ['vrf context {0}'.format(w['vrf'])]
         config = netcfg.get_section(parents)
         if not isinstance(config, list):
             config = config.split('\n')
         config = [line.strip() for line in config]
-        if set_command in config and state == 'absent':
+        if any(yrc in s for s in config) and state == 'absent':
             commands = [remove_command]
         elif set_command not in config and state == 'present':
-            commands = [set_command]
+            if any(yrc in s for s in config):
+                commands = [remove_command, set_command]
+            else:
+                commands = [set_command]
 
     if commands:
         candidate.add(commands, parents=parents)
 
 
-def fix_prefix_to_regex(prefix):
-    prefix = prefix.replace('.', r'\.').replace('/', r'\/')
-    return prefix
-
-
-def get_existing(module, prefix, warnings):
-    key_map = ['tag', 'pref', 'route_name', 'next_hop']
-    netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
-    parents = 'vrf context {0}'.format(module.params['vrf'])
-    prefix_to_regex = fix_prefix_to_regex(prefix)
-
-    route_regex = r'.*ip\sroute\s{0}\s(?P<next_hop>\S+)(\sname\s(?P<route_name>\S+))?(\stag\s(?P<tag>\d+))?(\s(?P<pref>\d+))?.*'.format(prefix_to_regex)
-
-    if module.params['vrf'] == 'default':
-        config = str(netcfg)
-    else:
-        config = netcfg.get_section(parents)
-
-    if config:
-        try:
-            match_route = re.match(route_regex, config, re.DOTALL)
-            group_route = match_route.groupdict()
-
-            for key in key_map:
-                if key not in group_route:
-                    group_route[key] = ''
-            group_route['prefix'] = prefix
-            group_route['vrf'] = module.params['vrf']
-        except (AttributeError, TypeError):
-            group_route = {}
-    else:
-        group_route = {}
-        msg = ("VRF {0} didn't exist.".format(module.params['vrf']))
-        if msg not in warnings:
-            warnings.append(msg)
-
-    return group_route
-
-
 def remove_route_command(prefix, w):
     return 'no ip route {0} {1}'.format(prefix, w['next_hop'])
 
@@ -172,11 +147,12 @@ def remove_route_command(prefix, w):
 def set_route_command(prefix, w):
     route_cmd = 'ip route {0} {1}'.format(prefix, w['next_hop'])
 
-    if w['route_name']:
+    if w['route_name'] and w['route_name'] != 'default':
         route_cmd += ' name {0}'.format(w['route_name'])
     if w['tag']:
-        route_cmd += ' tag {0}'.format(w['tag'])
-    if w['pref']:
+        if w['tag'] != 'default' and w['tag'] != '0':
+            route_cmd += ' tag {0}'.format(w['tag'])
+    if w['pref'] and w['pref'] != 'default':
         route_cmd += ' {0}'.format(w['pref'])
 
     return route_cmd


Test patch:
diff --git a/test/integration/targets/nxos_static_route/defaults/main.yaml b/test/integration/targets/nxos_static_route/defaults/main.yaml
--- a/test/integration/targets/nxos_static_route/defaults/main.yaml
+++ b/test/integration/targets/nxos_static_route/defaults/main.yaml
@@ -1,2 +1,5 @@
 ---
 testcase: "*"
+vrfs:
+  - default
+  - myvrf
diff --git a/test/integration/targets/nxos_static_route/tests/common/sanity.yaml b/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
@@ -11,8 +11,9 @@
       route_name: testing
       pref: 100
       tag: 5500
-      vrf: testing
+      vrf: "{{ item }}"
       provider: "{{ connection }}"
+    with_items: "{{ vrfs }}"
     register: result
 
   - assert: &true
@@ -21,28 +22,51 @@
 
   - name: "Conf Idempotence"
     nxos_static_route: *configure
+    with_items: "{{ vrfs }}"
     register: result
 
   - assert: &false
       that:
         - "result.changed == false"
 
+  - name: change static route
+    nxos_static_route: &configure1
+      prefix: "192.168.20.64/24"
+      next_hop: "3.3.3.3"
+      route_name: default
+      pref: 10
+      tag: default
+      vrf: "{{ item }}"
+      provider: "{{ connection }}"
+    with_items: "{{ vrfs }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Conf1 Idempotence"
+    nxos_static_route: *configure1
+    with_items: "{{ vrfs }}"
+    register: result
+
+  - assert: *false
+
   - name: remove static route
     nxos_static_route: &remove
       prefix: "192.168.20.64/24"
       next_hop: "3.3.3.3"
       route_name: testing
       pref: 100
-      tag: 5500
-      vrf: testing
+      vrf: "{{ item }}"
       state: absent
       provider: "{{ connection }}"
+    with_items: "{{ vrfs }}"
     register: result
 
   - assert: *true
 
   - name: "Remove Idempotence"
     nxos_static_route: *remove
+    with_items: "{{ vrfs }}"
     register: result
 
   - assert: *false
@@ -96,9 +120,10 @@
       route_name: testing
       pref: 100
       tag: 5500
-      vrf: testing
+      vrf: "{{ item }}"
       state: absent
       provider: "{{ connection }}"
+    with_items: "{{ vrfs }}"
     ignore_errors: yes
 
   - name: remove static route aggregate
diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt
--- a/test/sanity/validate-modules/ignore.txt
+++ b/test/sanity/validate-modules/ignore.txt
@@ -1326,7 +1326,6 @@ lib/ansible/modules/network/nxos/nxos_pim_interface.py E326
 lib/ansible/modules/network/nxos/nxos_pim_rp_address.py E326
 lib/ansible/modules/network/nxos/nxos_reboot.py E325
 lib/ansible/modules/network/nxos/nxos_smu.py E324
-lib/ansible/modules/network/nxos/nxos_static_route.py E324
 lib/ansible/modules/network/nxos/nxos_system.py E325
 lib/ansible/modules/network/nxos/nxos_vpc.py E324
 lib/ansible/modules/network/nxos/nxos_vpc_interface.py E325