Repository: ansible/ansible

Issue:
(P2)nxos_bgp_neighbor - several properties not idempotent
<!---
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/feature -->
- nxos_bgp_neighbor

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
 ansible 2.4.0 (devel 6bed831f8c) last updated 2017/06/27 15:24:19 (GMT -400)
  config file = None
  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: Nexus9000 C9508  running 7.0(3)I6(1)

##### SUMMARY
<!--- Explain the problem briefly -->
Several properties are not idempotent

##### 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
---
cli:
  host: "{{ inventory_hostname }}"
  username: "{{ nxos_cli_user | default('admin') }}"
  password: "{{ nxos_cli_pass | default('admin') }}"

nxapi:
  host: "{{ inventory_hostname }}"
  username: "{{ nxos_nxapi_user | default('admin') }}"
  password: "{{ nxos_nxapi_pass | default('admin') }}"
  transport: nxapi
  user_ssl: no

---
- name: NXOS Sample Playbook
  hosts: nxos
  connection: local
  gather_facts: no

  tasks:

  - name: Ensure BGP Is Enabled
    nxos_feature:
      feature: bgp
      state: enabled
      provider: "{{ cli }}"

  - name: "Configure BGP neighbor"
    nxos_bgp_neighbor: &configure_non_default
      asn: 65535
      neighbor: 3.3.3.3
      description: "tested by ansible"
      connected_check: true
      capability_negotiation: true
      dynamic_capability: true
      ebgp_multihop: 2
      log_neighbor_changes: enable
      low_memory_exempt: true
      remote_as: 12.1
      remove_private_as: all
      suppress_4_byte_as: true
      update_source: loopback151
      state: present
      provider: "{{ cli }}"
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
expected playbook to be ok the second time instead of 'changed'.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
PLAY [NXOS Sample Playbook] ***********************************************************************************************************************************************************************************************************************************

TASK [Ensure BGP Is Enabled] **********************************************************************************************************************************************************************************************************************************
ok: [nxxxx-xxx.example.com]

TASK [Configure BGP neighbor] *********************************************************************************************************************************************************************************************************************************
changed: [nxxxx-xxx.example.com]

PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************
nxxxx-xxx.example.com          : ok=2    changed=1    unreachable=0    failed=0
```



Discussion:
cc @jedelman8 @GGabriele @privateip @rcarrillocruz @mikewiebe @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Hi @rahushen. What version of Ansible are you using?
needs_info
@s-hertel updated issue with ansible version.
Could you please be more specific about which properties are not idempotent? (ie, by showing the contents of results.commands across successive runs)

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py b/lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py
--- a/lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py
+++ b/lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py
@@ -225,7 +225,7 @@
     'dynamic_capability',
     'low_memory_exempt',
     'suppress_4_byte_as',
-    'transport_passive_only'
+    'transport_passive_only',
 ]
 PARAM_TO_COMMAND_KEYMAP = {
     'asn': 'router bgp',
@@ -261,8 +261,8 @@
 
 def get_value(arg, config):
     command = PARAM_TO_COMMAND_KEYMAP[arg]
-    has_command = re.search(r'\s+{0}\s*$'.format(command), config, re.M)
-    has_command_value = re.search(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M)
+    has_command = re.search(r'^\s+{0}$'.format(command), config, re.M)
+    has_command_val = re.search(r'(?:\s+{0}\s*)(?P<value>.*)$'.format(command), config, re.M)
 
     if arg == 'dynamic_capability':
         has_no_command = re.search(r'\s+no\s{0}\s*$'.format(command), config, re.M)
@@ -276,23 +276,21 @@ def get_value(arg, config):
     elif arg == 'log_neighbor_changes':
         value = ''
         if has_command:
-            if has_command_value:
-                value = 'disable'
-            else:
-                value = 'enable'
+            value = 'enable'
+        elif has_command_val:
+            value = 'disable'
 
     elif arg == 'remove_private_as':
         value = 'disable'
         if has_command:
-            if has_command_value:
-                value = has_command_value.group('value')
-            else:
-                value = 'enable'
+            value = 'enable'
+        elif has_command_val:
+            value = has_command_val.group('value')
     else:
         value = ''
 
-        if has_command_value:
-            value = has_command_value.group('value')
+        if has_command_val:
+            value = has_command_val.group('value')
 
             if command in ['timers', 'password']:
                 split_value = value.split()
@@ -495,7 +493,7 @@ def main():
 
     if candidate:
         candidate = candidate.items_text()
-        load_config(module, candidate)
+        warnings.extend(load_config(module, candidate))
         result['changed'] = True
         result['commands'] = candidate
     else:


Test patch:
diff --git a/test/units/modules/network/nxos/fixtures/nxos_bgp/config.cfg b/test/units/modules/network/nxos/fixtures/nxos_bgp/config.cfg
--- a/test/units/modules/network/nxos/fixtures/nxos_bgp/config.cfg
+++ b/test/units/modules/network/nxos/fixtures/nxos_bgp/config.cfg
@@ -7,6 +7,8 @@ router bgp 65535
   vrf test2
     address-family ipv4 unicast
     timers bgp 1 10
+  neighbor 3.3.3.4
+    remove-private-as all
   neighbor 3.3.3.5
     address-family ipv4 unicast
       maximum-prefix 30 30
diff --git a/test/units/modules/network/nxos/test_nxos_bgp_neighbor.py b/test/units/modules/network/nxos/test_nxos_bgp_neighbor.py
--- a/test/units/modules/network/nxos/test_nxos_bgp_neighbor.py
+++ b/test/units/modules/network/nxos/test_nxos_bgp_neighbor.py
@@ -42,10 +42,17 @@ def tearDown(self):
         self.mock_get_config.stop()
 
     def load_fixtures(self, commands=None, device=''):
-        self.get_config.return_value = load_fixture('', 'nxos_bgp_config.cfg')
-        self.load_config.return_value = None
+        self.get_config.return_value = load_fixture('nxos_bgp', 'config.cfg')
+        self.load_config.return_value = []
 
     def test_nxos_bgp_neighbor(self):
         set_module_args(dict(asn=65535, neighbor='3.3.3.3', description='some words'))
-        result = self.execute_module(changed=True)
-        self.assertEqual(result['commands'], ['router bgp 65535', 'neighbor 3.3.3.3', 'description some words'])
+        self.execute_module(changed=True, commands=['router bgp 65535', 'neighbor 3.3.3.3', 'description some words'])
+
+    def test_nxos_bgp_neighbor_remove_private_as(self):
+        set_module_args(dict(asn=65535, neighbor='3.3.3.4', remove_private_as='all'))
+        self.execute_module(changed=False, commands=[])
+
+    def test_nxos_bgp_neighbor_remove_private_as_changed(self):
+        set_module_args(dict(asn=65535, neighbor='3.3.3.4', remove_private_as='replace-as'))
+        self.execute_module(changed=True, commands=['router bgp 65535', 'neighbor 3.3.3.4', 'remove-private-as replace-as'])