Repository: ansible/ansible

Issue:
ios_command & ios_config: copy running-config startup-config timeout due to lack of "file prompt quiet"
<!---
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 -->
ios_command and ios_config

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.2.0
  config file = /home/administrator/ansible/playbooks/ansible.cfg
  configured module search path = ['/usr/share/ansible/', '/home/administrator/ansible/playbooks']

```

##### CONFIGURATION
inventory      = $HOME/ansible/playbooks/hosts
library        = /usr/share/ansible/:/home/administrator/ansible/playbooks
retry_files_enabled = False

##### OS / ENVIRONMENT
Ubuntu 14.4 as control node
```
isr-2911-a# show ver | inc image
System image file is "flash0:c2900-universalk9-mz.SPA.153-3.M5.bin"
isr-2911-b>show ver | inc image
System image file is "flash0:c2900-universalk9-mz.SPA.153-3.M5.bin"
ISR-2911-D#show ver | inc image
System image file is "flash0:/c2900-universalk9-mz.SPA.154-2.T.bin"
```

##### SUMMARY
Encountering timeout trying to send command: `copy running-config startup-config`. Other commands are send  as expected.

##### 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.
-->
Please see this [Gist](https://gist.github.com/joelwking/b06ec9a6cd048c5d0e24644f6f62c392) . It demonstrates the playbook working as expected, then when the 
`save: yes` option is added, it fails on some, but not all hosts. I also tried using the ios_command module, and also encountered timeouts.
```
- name: save running config
    ios_command:
      provider: "{{ provider }}"
      auth_pass: "{{ ios.enable_password }}"
      authorize: yes
      commands:
       - "copy running-config startup-config"
```
##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
See [Gist](https://gist.github.com/joelwking/b06ec9a6cd048c5d0e24644f6f62c392)
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
See [Gist](https://gist.github.com/joelwking/b06ec9a6cd048c5d0e24644f6f62c392)
<!--- Paste verbatim command output between quotes below -->



Discussion:
Hi @joelwking Thanks for the bug report,

Could you try adding `timeout: 120` (or another maybe a higher value) as part of the `save running config` task.

needs_info
The failing routers do not have `file prompt quiet` configured. The successful router did have this command specified. By default, IOS will prompt for the destination filename before saving the configuration.
```
isr-2911-b#show run | inc file prompt
isr-2911-b#copy running-config startup-config
Destination filename [startup-config]?               < ==== IOS Prompting for destination filename
Building configuration...
[OK]
isr-2911-b#config t
Enter configuration commands, one per line.  End with CNTL/Z.
isr-2911-b(config)#file prompt quiet
isr-2911-b(config)#end
isr-2911-b#copy run start
Building configuration...
[OK]
```
After verifying all routers have `file prompt quiet`, execute this playbook:
```
#!/usr/bin/ansible-playbook
---
- hosts: IOS_routers
  gather_facts: no
  connection: local

  vars:

   ntp:
      - ntp server vrf management 10.255.0.1 prefer
      - ntp server vrf management 192.0.2.1

  tasks:

  - name: Decrypt the password file
    include_vars: "./passwords.yml"

  - name: SYS | Define provider
    set_fact:
      provider:
        host: "{{ inventory_hostname }}"
        username: "{{ ios.username }}"
        password: "{{ ios.password }}"

  - name: IOS | Show NTP
    ios_command:
      provider: "{{ provider }}"
      commands:
        - show ntp associations
    register: clock

  - name: show ntp associations
    debug: msg="show ntp associations {{clock.stdout}}"

  - name: TLCE | Correct NTP Server Config
    ios_config:
     provider: "{{ provider }}"
     timeout: 31
     auth_pass: "{{ ios.enable_password }}"
     authorize: yes
     config:
     lines:
       "{{ ntp }}"
     match: strict
     replace: line
     save: yes

  - name: TLCE | Verify NTP Config
    ios_command:
      provider: "{{ provider }}"
      auth_pass: "{{ ios.enable_password }}"
      authorize: yes
      commands:
       - show run | include ntp
    register: ntp

  - debug: msg="{{ ntp.stdout }}"

  - name: save running config
    ios_command:
      provider: "{{ provider }}"
      timeout: 31
      auth_pass: "{{ ios.enable_password }}"
      authorize: yes
      commands:
       - "copy running-config startup-config"
```
All device configuration are saved successfully:
```
administrator@Davis:~/ansible/playbooks$ ./toyota_ntp.yml

PLAY [IOS_routers] *************************************************************

TASK [Decrypt the password file] ***********************************************
ok: [isr-2911-a.sandbox.wwtatc.local]
ok: [isr-2911-b.sandbox.wwtatc.local]
ok: [isr-2911-d.sandbox.wwtatc.local]

TASK [SYS | Define provider] ***************************************************
ok: [isr-2911-a.sandbox.wwtatc.local]
ok: [isr-2911-b.sandbox.wwtatc.local]
ok: [isr-2911-d.sandbox.wwtatc.local]

TASK [IOS | Show NTP] **********************************************************
ok: [isr-2911-a.sandbox.wwtatc.local]
ok: [isr-2911-b.sandbox.wwtatc.local]
ok: [isr-2911-d.sandbox.wwtatc.local]

TASK [show ntp associations] ***************************************************
ok: [isr-2911-a.sandbox.wwtatc.local] => {
    "msg": "show ntp associations [u'\\n  address         ref clock       st   when   poll reach  delay  offset   disp\\n+~10.255.138.65   129.6.15.30      2     25     64   377  1.661 -22.978  5.094\\n*~10.255.0.1      129.6.15.29      2     59     64   177  0.641   0.218  5.041\\n ~192.0.2.1       .INIT.          16      -     64     0  0.000   0.000 15937.\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured']"
}
ok: [isr-2911-b.sandbox.wwtatc.local] => {
    "msg": "show ntp associations [u'\\n  address         ref clock       st   when   poll reach  delay  offset   disp\\n+~10.255.138.65   129.6.15.30      2     92    128   377  1.173 -19.810  2.615\\n*~10.255.0.1      129.6.15.28      2    130    128    77  0.658   4.947  1.813\\n ~192.0.2.1       .INIT.          16      -     64     0  0.000   0.000 15937.\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured']"
}
ok: [isr-2911-d.sandbox.wwtatc.local] => {
    "msg": "show ntp associations [u'\\n  address         ref clock       st   when   poll reach  delay  offset   disp\\n*~10.255.138.65   129.6.15.30      2    492   1024   377  1.606  -1.116  0.078\\n ~10.255.0.1      .INIT.          16      -     64     0  0.000   0.000 15937.\\n ~192.0.2.1       .INIT.          16      -     64     0  0.000   0.000 15937.\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured']"
}

TASK [TLCE | Correct NTP Server Config] ****************************************
changed: [isr-2911-a.sandbox.wwtatc.local]
changed: [isr-2911-b.sandbox.wwtatc.local]
changed: [isr-2911-d.sandbox.wwtatc.local]

TASK [TLCE | Verify NTP Config] ************************************************
ok: [isr-2911-a.sandbox.wwtatc.local]
ok: [isr-2911-d.sandbox.wwtatc.local]
ok: [isr-2911-b.sandbox.wwtatc.local]

TASK [debug] *******************************************************************
ok: [isr-2911-a.sandbox.wwtatc.local] => {
    "msg": [
        "ntp server vrf management 10.255.138.65\nntp server vrf management 10.255.0.1 prefer\nntp server vrf management 192.0.2.1"
    ]
}
ok: [isr-2911-b.sandbox.wwtatc.local] => {
    "msg": [
        "ntp server vrf management 10.255.138.65\nntp server vrf management 10.255.0.1 prefer\nntp server vrf management 192.0.2.1"
    ]
}
ok: [isr-2911-d.sandbox.wwtatc.local] => {
    "msg": [
        "ntp server vrf mgmtVrf 10.255.138.65\nntp server vrf management 10.255.0.1 prefer\nntp server vrf management 192.0.2.1"
    ]
}

TASK [save running config] *****************************************************
ok: [isr-2911-a.sandbox.wwtatc.local]
ok: [isr-2911-d.sandbox.wwtatc.local]
ok: [isr-2911-b.sandbox.wwtatc.local]

PLAY RECAP *********************************************************************
isr-2911-a.sandbox.wwtatc.local : ok=8    changed=1    unreachable=0    failed=0
isr-2911-b.sandbox.wwtatc.local : ok=8    changed=1    unreachable=0    failed=0
isr-2911-d.sandbox.wwtatc.local : ok=8    changed=1    unreachable=0    failed=0
```
The root problem would appear to be how the modules handle the IOS prompt for the destination configuration file.
Thanks for the further investigation, that's great.. 
For `delete` there is a `/noprompt` option, that doesn't appear to exist for `copy
So I wonder if we need to set `file prompt quiet` for this tasks (though not save that change)
We are very close to releasing 2.3.0 (final RC is due this week), so it's a bit late to fix this, which is a bug that's existed since `2.2.0`, therefore I've marked this as Milestone 2.4.

Once we have a fix we can look at backporting the fix to `stable-2.3` and `stable-2.2`
This is fixed here it seems: https://github.com/ansible/ansible/pull/21344

Although I agree with this post, it would be better to use `write mem`: https://github.com/ansible/ansible-modules-core/issues/5520
cc @privateip @rcarrillocruz
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Can you please test this on 2.4 and let us know if the issue persists?

Thanks
cc @Qalthos @ganeshrn @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
needs_info
This is still an issue in 2.4 using the new save_when option:
    "msg": "timeout trying to send command: copy running-config startup-config",

Using the now-deprecated save option in 2.3.2.0 did work as there was the extra '\r' after 'copy running-config startup-config'
**ISSUE TYPE**

- Bugfix

**COMPONENT NAME**

ios_config

**ANSIBLE VERSION**

2.4.0.0

**SUMMARY**

Using module "save_when" runs to a timeout using "copy running-config startup-config" command. The command "copy running-config startup-config" prompts for a destination filename, which was not handled.

> testswitch#copy running-config startup-config
> Destination filename [startup-config]?

**PROPOSED FIX**

in _IOS_CONFIG.PY_ : 

Add line return after command

```
if module.params['save_when'] != 'never':
        output = run_commands(module, ['show running-config', 'show startup-config'])

        running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines)
        startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines)

        if running_config.sha1 != startup_config.sha1 or module.params['save_when'] == 'always':
            result['changed'] = True
            if not module.check_mode:
                run_commands(module, 'copy running-config startup-config\r')
            else:
                module.warn('Skipping command `copy running-config startup-config` '
                            'due to check_mode.  Configuration not copied to '
                            'non-volatile storage')

```

cc @kedarX
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/network/ios/ios_config.py b/lib/ansible/modules/network/ios/ios_config.py
--- a/lib/ansible/modules/network/ios/ios_config.py
+++ b/lib/ansible/modules/network/ios/ios_config.py
@@ -483,7 +483,7 @@ def main():
         if running_config.sha1 != startup_config.sha1 or module.params['save_when'] == 'always':
             result['changed'] = True
             if not module.check_mode:
-                run_commands(module, 'copy running-config startup-config')
+                run_commands(module, 'copy running-config startup-config\r')
             else:
                 module.warn('Skipping command `copy running-config startup-config` '
                             'due to check_mode.  Configuration not copied to '


Test patch:
diff --git a/test/integration/targets/ios_config/tests/cli/save.yaml b/test/integration/targets/ios_config/tests/cli/save.yaml
--- a/test/integration/targets/ios_config/tests/cli/save.yaml
+++ b/test/integration/targets/ios_config/tests/cli/save.yaml
@@ -34,10 +34,33 @@
 # FIXME https://github.com/ansible/ansible-modules-core/issues/5008
   ignore_errors: true
 
+- name: delete config (setup)
+  ios_config:
+    replace: line
+    lines:
+      - "ip http server"
+    save_when: modified
+    authorize: yes
+  register: result
+
+- name: save should always run
+  ios_config:
+    replace: line
+    lines:
+      - "ip http server"
+    save_when: modified
+    authorize: yes
+  register: result
+
 - assert:
     that:
       - "result.changed == true"
-# FIXME https://github.com/ansible/ansible-modules-core/issues/5008
-  ignore_errors: true
+
+- name: teardown
+  ios_config:
+    lines:
+      - "no ip http server"
+    authorize: yes
+  register: result
 
 - debug: msg="END cli/save.yaml"
diff --git a/test/units/modules/network/ios/test_ios_config.py b/test/units/modules/network/ios/test_ios_config.py
--- a/test/units/modules/network/ios/test_ios_config.py
+++ b/test/units/modules/network/ios/test_ios_config.py
@@ -76,7 +76,7 @@ def test_ios_config_save_always(self):
         self.assertEqual(self.get_config.call_count, 0)
         self.assertEqual(self.load_config.call_count, 0)
         args = self.run_commands.call_args[0][1]
-        self.assertIn('copy running-config startup-config', args)
+        self.assertIn('copy running-config startup-config\r', args)
 
     def test_ios_config_lines_wo_parents(self):
         set_module_args(dict(lines=['hostname foo']))