Repository: ansible/ansible

Issue:
junos_config commit/rollback not working
<!---
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 -->
junos_config

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.3.2.0
  config file = /virtualenv/ansible23/ansiblenet/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Jul 18 2017, 09:17:00) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)]
```

##### 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).

-->
[defaults]
forks = 25
host_key_checking = False
remote_tmp = /tmp/${USER}/.ansible/tmp
inventory = ./environments/production
retry_files_enabled = False
filter_plugins = .
callback_whitelist = slack

##### 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.
-->
Control host: Apple macOS 10.12.6 (Sierra)
Remote device: Juniper Junos 15.1R5.5

##### SUMMARY
<!--- Explain the problem briefly -->
Want playbook to either commit or rollback config based on tag provided, result is nothing happens on the remote device.  I was able to workaround the commit issue by using the below, though I don't currently have a valid workaround for rollback due to another issue (that I will be opening separately from this as it pertains to the junos_rpc module).
```
- junos_command:
        provider: "{{login}}"
        rpcs: commit-configuration
```

##### 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.
-->
After making a change to the device and using `commit confirmed`, run the playbook using the below.

`ansible-playbook playbook.yml -t commit -e "host=groupname"`
or
`ansible-playbook playbook.yml -t rollback -e "host=groupname rollback=2"`

<!--- Paste example playbooks or commands between quotes below -->
```yaml
---
- hosts: "{{host}}"
  connection: local
  gather_facts: no
  vars_prompt:
      - name: "login_pw"
        prompt: "Password"
        private: yes
  vars:
      login:
          host: "{{ansible_host}}"
          username: "{{lookup('env','USER')}}"
          password: "{{login_pw}}"
          transport: netconf
          timeout: 20
  tasks:
      - name: Commit junos config
        junos_config:
            provider: "{{login}}"
            confirm: 0
        tags: commit

      - name: Rollback junos config
        junos_config:
            provider: "{{login}}"
            rollback: "{{rollback | default(1)}}"
        tags: rollback
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
The config to rollback or commit, depending on tag used.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Ansible outputs either "ok" for commit, or "changed" for rollback -- which is expected.  However, on the device, nothing has actually occurred.

<!--- Paste verbatim command output between quotes below -->
```
TASK [Rollback junos config] ***********************************************************************************************************************************************************************************************************
changed: [device1] => {
    "changed": true,
    "invocation": {
        "module_args": {
            "backup": false,
            "comment": "configured by junos_config",
            "confirm": 0,
            "host": null,
            "lines": null,
            "password": null,
            "port": null,
            "provider": {
                "host": "device1",
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
                "port": null,
                "ssh_keyfile": null,
                "timeout": 20,
                "transport": "netconf",
                "username": "test"
            },
            "replace": null,
            "rollback": 2,
            "src": null,
            "src_format": null,
            "ssh_keyfile": null,
            "timeout": 10,
            "transport": null,
            "update": "merge",
            "username": null,
            "zeroize": false
        }
    }
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************
device1                : ok=1    changed=1    unreachable=0    failed=0
```



Discussion:
cc @Qalthos @ganeshrn @gundalow @privateip @rcarrillocruz @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@mattlan `confirm: 0` will issue a `commit` command on device. There is no change in configuration (as `junos_config` task doesn't have any config related params) it will return ok. 
You can run the playbook with `--diff` option to see the config diff.

For rollback, you can issue below command on the device in config mode to check if config has changed across commits.
```
# show | compare rollback 2
```
If there is no change in config across commit, the configuration will not be changed on the device. 
However, since rollback is applied by ansible run, it will output ` changed` for rollback.

needs_info
I am not following your response, as I stated in my description I have pushed a change separate to the attached playbook using `commit confirmed 5`, until this is committed it will rollback after the timer expires automatically.  I am attempting to commit _that_ change using this playbook.  Using the method that worked in 2.2 -- `confirm: 0` -- does **not** commit the change on the device in 2.3.  Using the rpc command directly works as expected, also as stated in my description.  Since I have a workaround using rpc, this is less important than the below.  Though I suspect the two issues are related.

Additionally, you didn't address why the config isn't rolled back when using `rollback: X`, as it exhibits the same behavior as the above commit -- nothing as far as the device is concerned.
My bad, I missed the commit confirmed part in your comment.
If I understand it correctly you are trying to confirm a previous commit. 

If that's the case this feature is not available in 2.3. From 2.2 to 2.3 there was a major refactor for `junos_config` module and it was probably missed out.

It is fixed as part of https://github.com/ansible/ansible/issues/26433 and is available in 2.4 release.

So for 2.4 need to use `confirm_commit: yes` to confirm a previous commit.
```
- name: confirm a previous commit
  junos_config:
    confirm_commit: yes
```
On 2.3 a probable workaround would be to add a dummy config line and add `commit:0`


That is good information to know, explains why the commit didn't work.  My rpc workaround is good for now, so I am alright with that.  My real outstanding issue is the rollback not working, as I do not have a viable workaround for that piece.
It seems rollback is broken in both 2.3 and 2.4.
I will raise a PR to fix it on devel branch and backport it to stable-2.4

Resolving patch:
diff --git a/lib/ansible/module_utils/junos.py b/lib/ansible/module_utils/junos.py
--- a/lib/ansible/module_utils/junos.py
+++ b/lib/ansible/module_utils/junos.py
@@ -174,9 +174,9 @@ def locked_config(module):
         unlock_configuration(module)
 
 
-def get_diff(module):
+def get_diff(module, rollback='0'):
 
-    reply = get_configuration(module, compare=True, format='text')
+    reply = get_configuration(module, compare=True, format='text', rollback=rollback)
     # if warning is received from device diff is empty.
     if isinstance(reply, list):
         return None
diff --git a/lib/ansible/modules/network/junos/junos_config.py b/lib/ansible/modules/network/junos/junos_config.py
--- a/lib/ansible/modules/network/junos/junos_config.py
+++ b/lib/ansible/modules/network/junos/junos_config.py
@@ -191,7 +191,7 @@
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.junos import get_diff, load_config, get_configuration
 from ansible.module_utils.junos import commit_configuration, discard_changes, locked_config
-from ansible.module_utils.junos import junos_argument_spec
+from ansible.module_utils.junos import junos_argument_spec, load_configuration
 from ansible.module_utils.junos import check_args as junos_check_args
 from ansible.module_utils.netconf import send_request
 from ansible.module_utils.six import string_types
@@ -227,8 +227,8 @@ def zeroize(ele):
     return send_request(ele, Element('request-system-zeroize'))
 
 
-def rollback(ele):
-    return get_diff(ele)
+def rollback(ele, id='0'):
+    return get_diff(ele, id)
 
 
 def guess_format(config):
@@ -346,9 +346,16 @@ def main():
 
         result['__backup__'] = match.text.strip()
 
-    if module.params['rollback']:
+    rollback_id = module.params['rollback']
+    if rollback_id:
+        diff = rollback(module, rollback_id)
         if commit:
-            diff = rollback(module)
+            kwargs = {
+                'comment': module.params['comment']
+            }
+            with locked_config(module):
+                load_configuration(module, rollback=rollback_id)
+                commit_configuration(module, **kwargs)
             if module._diff:
                 result['diff'] = {'prepared': diff}
         result['changed'] = True


Test patch:
diff --git a/test/integration/targets/junos_config/tests/netconf/single.yaml b/test/integration/targets/junos_config/tests/netconf/single.yaml
--- a/test/integration/targets/junos_config/tests/netconf/single.yaml
+++ b/test/integration/targets/junos_config/tests/netconf/single.yaml
@@ -41,6 +41,37 @@
     that:
       - "result.changed == true"
 
+- name: teardown for rollback test
+  junos_config:
+    lines:
+    - delete system syslog file test1
+    provider: "{{ netconf }}"
+
+- name: Configure syslog file
+  junos_config:
+    lines:
+    - set system syslog file test1 any any
+    provider: "{{ netconf }}"
+  register: result
+
+- assert:
+    that:
+      - "result.changed == true"
+      - result.diff.prepared | search("\+ *file test1")
+      - result.diff.prepared | search("\+ *any any")
+
+- name: Rollback junos config
+  junos_config:
+    rollback: 1
+    provider: "{{ netconf }}"
+  register: result
+
+- assert:
+    that:
+      - "result.changed == true"
+      - result.diff.prepared | search("\+ *file test1")
+      - result.diff.prepared | search("\+ *any any")
+
 - name: teardown
   junos_config:
     lines:
diff --git a/test/units/modules/network/junos/test_junos_config.py b/test/units/modules/network/junos/test_junos_config.py
--- a/test/units/modules/network/junos/test_junos_config.py
+++ b/test/units/modules/network/junos/test_junos_config.py
@@ -36,6 +36,9 @@ def setUp(self):
         self.mock_load_config = patch('ansible.modules.network.junos.junos_config.load_config')
         self.load_config = self.mock_load_config.start()
 
+        self.mock_load_configuration = patch('ansible.modules.network.junos.junos_config.load_configuration')
+        self.load_configuration = self.mock_load_configuration.start()
+
         self.mock_lock_configuration = patch('ansible.module_utils.junos.lock_configuration')
         self.lock_configuration = self.mock_lock_configuration.start()
 
@@ -59,6 +62,7 @@ def tearDown(self):
         self.mock_commit_configuration.stop()
         self.mock_get_diff.stop()
         self.mock_send_request.stop()
+        self.load_configuration.stop()
 
     def load_fixtures(self, commands=None, format='text', changed=False):
         self.get_config.return_value = load_fixture('get_configuration_rpc_reply.txt')
@@ -101,9 +105,14 @@ def test_junos_config_confirm(self):
         self.assertEqual(kwargs['confirm_timeout'], 40)
 
     def test_junos_config_rollback(self):
-        set_module_args(dict(rollback=10))
+        rollback = 10
+        set_module_args(dict(rollback=rollback))
         self.execute_module(changed=True)
         self.assertEqual(self.get_diff.call_count, 1)
+        self.assertEqual(self.load_configuration.call_count, 1)
+        self.assertEqual(self.commit_configuration.call_count, 1)
+        load_configuration_args = self.load_configuration.call_args
+        self.assertEqual(rollback, load_configuration_args[1].get('rollback'))
 
     def test_junos_config_src_text(self):
         src = load_fixture('junos_config.text', content='str')