Repository: ansible/ansible

Issue:
Unnecessary /bin/lsblk executions in the get_mount_facts function of the setup module
##### Issue Type:
- Bug Report
##### Ansible Version:

```
ansible 2.0.0.2
  config file = /Users/kilburn/Documents/Projects/tu/plataforma/ansible.cfg
  configured module search path = Default w/o overrides
```

```
ansible 2.1.0 (devel 517acb5773) last updated 2016/02/18 08:46:22 (GMT +200)
  lib/ansible/modules/core: (detached HEAD 8d126bd877) last updated 2016/02/18 08:46:32 (GMT +200)
  lib/ansible/modules/extras: (detached HEAD f6c5ed987f) last updated 2016/02/18 08:46:39 (GMT +200)
  config file = 
  configured module search path = Default w/o overrides
```
##### Ansible Configuration:

None relevant.
##### Environment:

Controller is OS X 10.10.5, the managed OS is Debian Jessie 8.3 (fully updated at the time of this writing).
##### Summary:

This is probably related to #10779. Function `get_mount_facts` times out on my test machine, throwing the following error:

```
test | FAILED! => {
    "changed": false, 
    "cmd": "/bin/lsblk -ln --output UUID /dev/dm-0", 
    "failed": true, 
    "msg": "Traceback (most recent call last):\n  File \"<stdin>\", line 1999, in run_command\n  File \"<stdin>\", line 2156, in _handle_timeout\nTimeoutError: Timer expired\n", 
    "rc": 257
}
```

The root cause is that, to setup multiple chroots, my test machine has many bind mounted paths. These paths appear in `/etc/mtab`, for instance:

```
/dev/dm-0 /srv/user1/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9 ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/dm-0 /srv/user2/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9 ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/dm-0 /srv/user3/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9 ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/dm-0 /srv/user4/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9 ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/dm-0 /srv/user5/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9 ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
...
```

In the current implementation, `get_mount_facts` calls the `lsblk` binary to get the uuid of each device (first field of a line starting with `/`, such as those lines above). This means that `lsblk` is being called here many times to get the _uuid_ of the same `/dev/dm-0` device. That many calls make the function end up timing out.

Since the _uuid_ of `/dev/dm-0` is always going to be the same, all these calls could be avoided by caching the results of the first `lsblk /dev/dm-0` execution.
##### Steps To Reproduce:

Setup a large number of bind-mounts on a test machine `test`. Run `ansible -m setup test`.
##### Expected Results:

You get the machine's facts.
##### Actual Results:

```
test | FAILED! => {
    "changed": false, 
    "cmd": "/bin/lsblk -ln --output UUID /dev/dm-0", 
    "failed": true, 
    "msg": "Traceback (most recent call last):\n  File \"<stdin>\", line 1999, in run_command\n  File \"<stdin>\", line 2156, in _handle_timeout\nTimeoutError: Timer expired\n", 
    "rc": 257
}
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/module_utils/facts.py b/lib/ansible/module_utils/facts.py
--- a/lib/ansible/module_utils/facts.py
+++ b/lib/ansible/module_utils/facts.py
@@ -1037,6 +1037,7 @@ def get_dmi_facts(self):
 
     @timeout(10)
     def get_mount_facts(self):
+        uuids = dict()
         self.facts['mounts'] = []
         mtab = get_file_content('/etc/mtab', '')
         for line in mtab.split('\n'):
@@ -1052,13 +1053,17 @@ def get_mount_facts(self):
                     except OSError:
                         continue
 
-                    uuid = 'NA'
-                    lsblkPath = module.get_bin_path("lsblk")
-                    if lsblkPath:
-                        rc, out, err = module.run_command("%s -ln --output UUID %s" % (lsblkPath, fields[0]), use_unsafe_shell=True)
-
-                        if rc == 0:
-                            uuid = out.strip()
+                    if fields[0] in uuids:
+                        uuid = uuids[fields[0]]
+                    else:
+                        uuid = 'NA'
+                        lsblkPath = module.get_bin_path("lsblk")
+                        if lsblkPath:
+                            rc, out, err = module.run_command("%s -ln --output UUID %s" % (lsblkPath, fields[0]), use_unsafe_shell=True)
+
+                            if rc == 0:
+                                uuid = out.strip()
+                                uuids[fields[0]] = uuid
 
                     self.facts['mounts'].append(
                         {'mount': fields[1],


Test patch: