Repository: ansible/ansible

Issue:
AIX WPAR handling is inefficient and possibly buggy
The `lib/ansible/module_utils/facts.py:AIXNetwork.get_interfaces_info` method is structured like:

```
rc, out, err = module.run_command([ifconfig_path, '-a'])
for line in out.split('\n'):
    ...
    uname_path = module.get_bin_path('uname')
    if uname_path:
        rc, out, err = module.run_command([uname_path, '-W'])
```

This has two big problems:
1. It uses `subprocess.Popen` to run `uname -W` _for each line in ifconfig_. This is a performance issue because shelling out is expensive and this data isn't going to change during run on a single host. It should be collected once and then reused.
2. Note that `out` is overwritten inside the loop. It won't break the loop, which is iterating over `out.split('\n')` and not `out` directly. However, that may cause breakage in further edits that expect `out` to keep the same information throughout the loop. Consider using unique variable names like `ifconfig_out`,`entstat_out`, etc. to avoid weird, hard-to-debug errors later.

/cc @bmanojlovic who contributed this code.



Discussion:
The code in devel (2.3) still has this inefficient behavior. I'm guessing it was just a mistaken indentation? https://github.com/ansible/ansible/pull/11290 was never merged and I suspect will never get rebased.

I don't have access to a machine to test this against anymore.


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
@@ -2837,6 +2837,14 @@ def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'):
             all_ipv4_addresses = [],
             all_ipv6_addresses = [],
         )
+
+        uname_rc = None
+        uname_out = None
+        uname_err = None
+        uname_path = self.module.get_bin_path('uname')
+        if uname_path:
+            uname_rc, uname_out, uname_err = self.module.run_command([uname_path, '-W'])
+
         rc, out, err = self.module.run_command([ifconfig_path, ifconfig_options])
 
         for line in out.splitlines():
@@ -2866,40 +2874,40 @@ def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'):
                     self.parse_inet6_line(words, current_if, ips)
                 else:
                     self.parse_unknown_line(words, current_if, ips)
-            uname_path = self.module.get_bin_path('uname')
-            if uname_path:
-                rc, out, err = self.module.run_command([uname_path, '-W'])
-                # don't bother with wpars it does not work
-                # zero means not in wpar
-                if not rc and out.split()[0] == '0':
-                    if current_if['macaddress'] == 'unknown' and re.match('^en', current_if['device']):
-                        entstat_path = self.module.get_bin_path('entstat')
-                        if entstat_path:
-                            rc, out, err = self.module.run_command([entstat_path, current_if['device'] ])
-                            if rc != 0:
-                                break
-                            for line in out.splitlines():
-                                if not line:
-                                    pass
-                                buff = re.match('^Hardware Address: (.*)', line)
-                                if buff:
-                                    current_if['macaddress'] = buff.group(1)
-
-                                buff = re.match('^Device Type:', line)
-                                if buff and re.match('.*Ethernet', line):
-                                    current_if['type'] = 'ether'
-                    # device must have mtu attribute in ODM
-                    if 'mtu' not in current_if:
-                        lsattr_path = self.module.get_bin_path('lsattr')
-                        if lsattr_path:
-                            rc, out, err = self.module.run_command([lsattr_path,'-El', current_if['device'] ])
-                            if rc != 0:
-                                break
-                            for line in out.splitlines():
-                                if line:
-                                    words = line.split()
-                                    if words[0] == 'mtu':
-                                        current_if['mtu'] = words[1]
+
+            # don't bother with wpars it does not work
+            # zero means not in wpar
+            if not uname_rc and uname_out.split()[0] == '0':
+
+                if current_if['macaddress'] == 'unknown' and re.match('^en', current_if['device']):
+                    entstat_path = self.module.get_bin_path('entstat')
+                    if entstat_path:
+                        rc, out, err = self.module.run_command([entstat_path, current_if['device'] ])
+                        if rc != 0:
+                            break
+                        for line in out.splitlines():
+                            if not line:
+                                pass
+                            buff = re.match('^Hardware Address: (.*)', line)
+                            if buff:
+                                current_if['macaddress'] = buff.group(1)
+
+                            buff = re.match('^Device Type:', line)
+                            if buff and re.match('.*Ethernet', line):
+                                current_if['type'] = 'ether'
+
+                # device must have mtu attribute in ODM
+                if 'mtu' not in current_if:
+                    lsattr_path = self.module.get_bin_path('lsattr')
+                    if lsattr_path:
+                        rc, out, err = self.module.run_command([lsattr_path,'-El', current_if['device'] ])
+                        if rc != 0:
+                            break
+                        for line in out.splitlines():
+                            if line:
+                                words = line.split()
+                                if words[0] == 'mtu':
+                                    current_if['mtu'] = words[1]
         return interfaces, ips
 
     # AIX 'ifconfig -a' does not inform about MTU, so remove current_if['mtu'] here


Test patch: