Repository: ansible/ansible

Issue:
nxos_ospf_vrf issues
<!---
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
-->
nxos_ospf_vrf
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0 (devel 788010d0f0) last updated 2018/01/08 11:49:21 (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)I5(2)

##### SUMMARY
<!--- Explain the problem briefly -->
There are multiple issues with nxos_ospf_vrf module
1. Setting parameters to default is not working in most cases.
2. Most of the parameters are not idempotent
3. There are 6 parameters related to timers and these are configured using 2 CLIs (3 timer values in each CLI). However, if the user tries to change just one or 2 values belonging to each CLI, it does not work. User is forced to provide all the 3 timer values whether they need to be changed or not. If they are not provided the module throws weird errors or the sets are not correct. This is not a desirable behavior. 

Note: I will open a PR shortly to address these above issues


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_ospf_vrf.py b/lib/ansible/modules/network/nxos/nxos_ospf_vrf.py
--- a/lib/ansible/modules/network/nxos/nxos_ospf_vrf.py
+++ b/lib/ansible/modules/network/nxos/nxos_ospf_vrf.py
@@ -180,42 +180,14 @@
     'timer_throttle_spf_start': '200',
     'timer_throttle_spf_max': '5000',
     'timer_throttle_spf_hold': '1000',
-    'auto_cost': '40000'
+    'auto_cost': '40000',
+    'default_metric': '',
+    'passive_interface': False,
+    'router_id': '',
+    'log_adjacency': '',
 }
 
 
-def get_value(arg, config, module):
-    command = PARAM_TO_COMMAND_KEYMAP.get(arg)
-    command_re = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(command), re.M)
-    value = ''
-
-    if command in config:
-        if arg == 'log_adjacency':
-            if 'log-adjacency-changes detail' in config:
-                value = 'detail'
-            else:
-                value = 'log'
-        elif arg == 'passive_interface':
-            if 'passive-interface default' in config:
-                value = True
-            else:
-                value = False
-        else:
-            value_list = command_re.search(config).group('value').split()
-            if 'hold' in arg:
-                value = value_list[1]
-            elif 'max' in arg:
-                value = value_list[2]
-            elif 'auto' in arg:
-                if 'Gbps' in value_list:
-                    value = str(int(value_list[0]) * 1000)
-                else:
-                    value = value_list[0]
-            else:
-                value = value_list[0]
-    return value
-
-
 def get_existing(module, args):
     existing = {}
     netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
@@ -225,6 +197,10 @@ def get_existing(module, args):
         parents.append('vrf {0}'.format(module.params['vrf']))
 
     config = netcfg.get_section(parents)
+    for arg in args:
+        if arg not in ['ospf', 'vrf']:
+            existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg)
+
     if config:
         if module.params['vrf'] == 'default':
             splitted_config = config.splitlines()
@@ -236,10 +212,35 @@ def get_existing(module, args):
             if vrf_index:
                 config = '\n'.join(splitted_config[0:vrf_index])
 
-        for arg in args:
-            if arg not in ['ospf', 'vrf']:
-                existing[arg] = get_value(arg, config, module)
-
+        splitted_config = config.splitlines()
+        for line in splitted_config:
+            if 'passive' in line:
+                existing['passive_interface'] = True
+            elif 'router-id' in line:
+                existing['router_id'] = re.search(r'router-id (\S+)', line).group(1)
+            elif 'metric' in line:
+                existing['default_metric'] = re.search(r'default-metric (\S+)', line).group(1)
+            elif 'adjacency' in line:
+                log = re.search(r'log-adjacency-changes(?: (\S+))?', line).group(1)
+                if log:
+                    existing['log_adjacency'] = log
+                else:
+                    existing['log_adjacency'] = 'log'
+            elif 'auto' in line:
+                cost = re.search(r'auto-cost reference-bandwidth (\d+) (\S+)', line).group(1)
+                if 'Gbps' in line:
+                    cost *= 1000
+                existing['auto_cost'] = str(cost)
+            elif 'timers throttle lsa' in line:
+                tmp = re.search(r'timers throttle lsa (\S+) (\S+) (\S+)', line)
+                existing['timer_throttle_lsa_start'] = tmp.group(1)
+                existing['timer_throttle_lsa_hold'] = tmp.group(2)
+                existing['timer_throttle_lsa_max'] = tmp.group(3)
+            elif 'timers throttle spf' in line:
+                tmp = re.search(r'timers throttle spf (\S+) (\S+) (\S+)', line)
+                existing['timer_throttle_spf_start'] = tmp.group(1)
+                existing['timer_throttle_spf_hold'] = tmp.group(2)
+                existing['timer_throttle_spf_max'] = tmp.group(3)
         existing['vrf'] = module.params['vrf']
         existing['ospf'] = module.params['ospf']
 
@@ -255,6 +256,13 @@ def apply_key_map(key_map, table):
     return new_dict
 
 
+def get_timer_prd(key, proposed):
+    if proposed.get(key):
+        return proposed.get(key)
+    else:
+        return PARAM_TO_DEFAULT_KEYMAP.get(key)
+
+
 def state_present(module, existing, proposed, candidate):
     commands = list()
     proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
@@ -271,23 +279,25 @@ def state_present(module, existing, proposed, candidate):
             else:
                 commands.append('no {0}'.format(key))
 
-        elif value == 'default':
-            if existing_commands.get(key):
+        elif value == 'default' or value == '':
+            if key == 'log-adjacency-changes':
+                commands.append('no {0}'.format(key))
+            elif existing_commands.get(key):
                 existing_value = existing_commands.get(key)
                 commands.append('no {0} {1}'.format(key, existing_value))
         else:
             if key == 'timers throttle lsa':
                 command = '{0} {1} {2} {3}'.format(
                     key,
-                    proposed['timer_throttle_lsa_start'],
-                    proposed['timer_throttle_lsa_hold'],
-                    proposed['timer_throttle_lsa_max'])
+                    get_timer_prd('timer_throttle_lsa_start', proposed),
+                    get_timer_prd('timer_throttle_lsa_hold', proposed),
+                    get_timer_prd('timer_throttle_lsa_max', proposed))
             elif key == 'timers throttle spf':
                 command = '{0} {1} {2} {3}'.format(
                     key,
-                    proposed['timer_throttle_spf_start'],
-                    proposed['timer_throttle_spf_hold'],
-                    proposed['timer_throttle_spf_max'])
+                    get_timer_prd('timer_throttle_spf_start', proposed),
+                    get_timer_prd('timer_throttle_spf_hold', proposed),
+                    get_timer_prd('timer_throttle_spf_max', proposed))
             elif key == 'log-adjacency-changes':
                 if value == 'log':
                     command = key
@@ -322,25 +332,47 @@ def state_absent(module, existing, proposed, candidate):
                 if key == 'passive-interface default':
                     command = 'no {0}'.format(key)
                 elif key == 'timers throttle lsa':
-                    command = 'no {0} {1} {2} {3}'.format(
-                        key,
-                        existing['timer_throttle_lsa_start'],
-                        existing['timer_throttle_lsa_hold'],
-                        existing['timer_throttle_lsa_max'])
+                    if (existing['timer_throttle_lsa_start'] !=
+                        PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_start') or
+                        existing['timer_throttle_lsa_hold'] !=
+                        PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_hold') or
+                        existing['timer_throttle_lsa_max'] !=
+                            PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_max')):
+                        command = 'no {0} {1} {2} {3}'.format(
+                            key,
+                            existing['timer_throttle_lsa_start'],
+                            existing['timer_throttle_lsa_hold'],
+                            existing['timer_throttle_lsa_max'])
                 elif key == 'timers throttle spf':
-                    command = 'no {0} {1} {2} {3}'.format(
-                        key,
-                        existing['timer_throttle_spf_start'],
-                        existing['timer_throttle_spf_hold'],
-                        existing['timer_throttle_spf_max'])
+                    if (existing['timer_throttle_spf_start'] !=
+                        PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_start') or
+                        existing['timer_throttle_spf_hold'] !=
+                        PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_hold') or
+                        existing['timer_throttle_spf_max'] !=
+                            PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_max')):
+                        command = 'no {0} {1} {2} {3}'.format(
+                            key,
+                            existing['timer_throttle_spf_start'],
+                            existing['timer_throttle_spf_hold'],
+                            existing['timer_throttle_spf_max'])
+                elif key == 'log-adjacency-changes':
+                    command = 'no {0}'.format(key)
+                elif key == 'auto-cost reference-bandwidth':
+                    if value != PARAM_TO_DEFAULT_KEYMAP.get('auto_cost'):
+                        command = 'no {0}'.format(key)
+                    else:
+                        command = None
                 else:
                     existing_value = existing_commands.get(key)
                     command = 'no {0} {1}'.format(key, existing_value)
 
-                if command not in commands:
-                    commands.append(command)
+                if command:
+                    if command not in commands:
+                        commands.append(command)
     else:
-        commands = ['no vrf {0}'.format(module.params['vrf'])]
+        if (existing.get('vrf') and
+                existing.get('vrf') == module.params['vrf']):
+            commands = ['no vrf {0}'.format(module.params['vrf'])]
 
     if commands:
         candidate.add(commands, parents=parents)


Test patch:
diff --git a/test/integration/targets/nxos_ospf_vrf/tests/common/sanity.yaml b/test/integration/targets/nxos_ospf_vrf/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_ospf_vrf/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_ospf_vrf/tests/common/sanity.yaml
@@ -39,15 +39,65 @@
       that:
         - "result.changed == false"
 
-  rescue:
-  - name: "Disable feature OSPF"
-    nxos_feature:
-      feature: ospf
-      state: disabled
+  - name: Configure ospf vrf
+    nxos_ospf_vrf: &config1
+      ospf: 2
+      auto_cost: 5000
+      timer_throttle_spf_hold: 1100
+      timer_throttle_lsa_max: 2222
+      default_metric: 1000
+      log_adjacency: log
+      vrf: default
+      passive_interface: true
+      state: present
       provider: "{{ connection }}"
-    ignore_errors: yes
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_ospf_vrf: *config1
+    register: result
+
+  - assert: *false
+
+  - name: Configure ospf vrf
+    nxos_ospf_vrf: &config2
+      ospf: 2
+      auto_cost: default
+      default_metric: default
+      log_adjacency: default
+      timer_throttle_spf_hold: default
+      passive_interface: false
+      vrf: default
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_ospf_vrf: *config2
+    register: result
+
+  - assert: *false
+
+  - name: Unconfigure ospf vrf
+    nxos_ospf_vrf: &unconfig1
+      ospf: 2
+      vrf: default
+      state: absent
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence"
+    nxos_ospf_vrf: *unconfig1
+    register: result
+
+  - assert: *false
 
-  always:
   - name: Unconfigure ospf vrf
     nxos_ospf_vrf: &unconfig
       ospf: 1
@@ -64,4 +114,12 @@
 
   - assert: *false
 
+  always:
+  - name: "Disable feature OSPF"
+    nxos_feature:
+      feature: ospf
+      state: disabled
+      provider: "{{ connection }}"
+    ignore_errors: yes
+
 - debug: msg="END connection={{ ansible_connection }} nxos_ospf_vrf sanity test"