lineinfile with serial is overwriting the data

lineinfile when used with serial is overwriting the data.

Here’s what I’m trying to do:
In a serial deployment there are ‘100’ hosts and 20 in each batch. I do not want to stop the execution when a task fails for one host. I’m using ignore_failure to ignore the failure and continue exec on other hosts. At the end of the exec I want to list all the hosts which failed to execute the task. I tried using a local var, I wasn’t successful. I tried writing the hostname to a file and at the end of playbook exec I want to print the file.

I’m able to do it with shell module >>. But I am unable to do it with lineinfile.

STEPS TO REPRODUCE
- hosts: webservers
  remote_user: sudo
  serial: "{{ serial_count | default('100%') }}"
  tasks:
    - name: update file
      lineinfile: hosts.txt line="{{ inventory_hostname }}"
      delegate_to: localhost

EXPECTED RESULTS

All the hostnames in the file

ACTUAL RESULTS

Only few hostnames, random every time.

I would like to know if there are any better approaches to do this.

Thanks.

Hi,

The lineinfile documentation says:
"Normally this module uses atomic operations to prevent data corruption or inconsistent reads from the target files, sometimes systems are configured or just broken in ways that prevent this."
http://docs.ansible.com/ansible/lineinfile_module.html

But I was able to reproduce your issue on a CentOS 6.8 machine with local filesystem.

I suppose this is due to the way lineinfile works:
https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/files/lineinfile.py

In order to add a line to a file, it needs to:
1. read the file into the memory (and close the file)
2. modify the content (in memory)
3. write the content to the temp file
4. replace the original file by the temp file (only point using the atomic_move)

When serial is > 1 and "delegate_to: localhost" is set there are two or more processes running the 1,2,3,4 above in parallel on the same machine (localhost). These actions are not done atomically, so one process (call it A) might read the hosts.txt, then another (call it B) would read it before the A has written to it, then A would write the change, and then B would write et voilà - the change from A is overwritten.

Maybe this should be reported to:
https://github.com/ansible/ansible/issues

Until it's fixed, I would propose a different method for achieving the same result:
1. create a temporary directory
2. touch a file inside the temp dir with the inventory_hostname as the filename (file module with state: touch)
3. at the end find the files inside the temp dir with find module and register result
4. delete the temp dir

Cheers,
Marko
CONFIDENTIALITY NOTICE: This message is the property of International Game Technology PLC and/or its subsidiaries and may contain proprietary, confidential or trade secret information. This message is intended solely for the use of the addressee. If you are not the intended recipient and have received this message in error, please delete this message from your system. Any unauthorized reading, distribution, copying, or other use of this message or its attachments is strictly prohibited.