Repository: ansible/ansible-modules-core

Issue:
race condition in directory creation using file module
Ansible 1.9.2 (also observed this issue on 1.7.2)

mybook.yml:

```

---
-
  hosts: all
  serial: 10
  tasks:
  -
    file:
      path: "/tmp/banana"
      state: "directory"
```

myhosts

```
lh0
lh1
lh2
lh3
lh4
lh5
lh6
lh7
lh8
lh9
```

NB all these are aliases of 127.0.0.1 in my `/etc/localhosts`

The following command will occasionally fail, in my experience about 5% failure rate, but YMMV:

```
rm -rf /tmp/banana ; ansible-playbook -i myhosts  mybook.yml
```

```
PLAY [all] ******************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [lh4]
ok: [lh0]
ok: [lh3]
ok: [lh1]
ok: [lh2]
ok: [lh6]
ok: [lh5]
ok: [lh7]
ok: [lh8]
ok: [lh9]

TASK: [file ] ***************************************************************** 
changed: [lh3]
failed: [lh0] => {"failed": true, "parsed": false}
Traceback (most recent call last):
  File "/home/ubuntu/.ansible/tmp/ansible-tmp-1435747470.28-279212074405278/file", line 2005, in <module>
    main()
  File "/home/ubuntu/.ansible/tmp/ansible-tmp-1435747470.28-279212074405278/file", line 279, in main
    os.mkdir(curpath)
OSError: [Errno 17] File exists: '/tmp/banana'
OpenSSH_6.6.1, OpenSSL 1.0.1f 6 Jan 2014
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: auto-mux: Trying existing master
debug1: mux_client_request_session: master session id: 2
Shared connection to lh0 closed.

ok: [lh1]
ok: [lh2]
ok: [lh4]
ok: [lh5]
ok: [lh6]
ok: [lh7]
ok: [lh9]
ok: [lh8]

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/home/ubuntu/mybook.retry

lh0                        : ok=1    changed=0    unreachable=0    failed=1   
lh1                        : ok=2    changed=0    unreachable=0    failed=0   
lh2                        : ok=2    changed=0    unreachable=0    failed=0   
lh3                        : ok=2    changed=1    unreachable=0    failed=0   
lh4                        : ok=2    changed=0    unreachable=0    failed=0   
lh5                        : ok=2    changed=0    unreachable=0    failed=0   
lh6                        : ok=2    changed=0    unreachable=0    failed=0   
lh7                        : ok=2    changed=0    unreachable=0    failed=0   
lh8                        : ok=2    changed=0    unreachable=0    failed=0   
lh9                        : ok=2    changed=0    unreachable=0    failed=0   
```

I have been able to automate the triggering of this with:

```
true; while [ $? == 0 ]; do rm -rf /tmp/banana ; ansible-playbook -i myhosts  mybook.yml ; done
```



Discussion:
Here's the offending code:

  https://github.com/ansible/ansible-modules-core/blob/devel/files/file.py#L282-L283

This is not atomic.


Resolving patch:
diff --git a/files/file.py b/files/file.py
--- a/files/file.py
+++ b/files/file.py
@@ -18,6 +18,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 
+import errno
 import shutil
 import stat
 import grp
@@ -280,7 +281,13 @@ def main():
                 if not os.path.isabs(path):
                     curpath = curpath.lstrip('/')
                 if not os.path.exists(curpath):
-                    os.mkdir(curpath)
+                    try:
+                        os.mkdir(curpath)
+                    except OSError, ex:
+                        # Possibly something else created the dir since the os.path.exists
+                        # check above. As long as it's a dir, we don't need to error out.
+                        if not (ex.errno == errno.EEXISTS and os.isdir(curpath)):
+                            raise
                     tmp_file_args = file_args.copy()
                     tmp_file_args['path']=curpath
                     changed = module.set_fs_attributes_if_different(tmp_file_args, changed)


Test patch: