Repository: ansible/ansible

Issue:
nxos_facts: AttributeError: 'dict' object has no attribute 'startswith'
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
nxos_fact

##### ANSIBLE VERSION
```

```

##### SUMMARY

##### STEPS TO REPRODUCE
```
cd test/integration
ANSIBLE_ROLES_PATH=targets ansible-playbook -vvvvv  -i  inventory.network nxos.yaml  -e "limit_to=nxos_facts"

```

##### EXPECTED RESULTS
Success

##### ACTUAL RESULTS
```yaml
TASK [nxos_facts : test getting all facts] *******************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/nxos_facts/tests/nxapi/all_facts.yaml:5
<nxos01> connection transport is nxapi
Using module file /home/johnb/git/ansible-inc/ansible-workspace-2/lib/ansible/modules/network/nxos/nxos_facts.py
<nxos01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<nxos01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<nxos01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126 `" && echo ansible-tmp-1491401561.48-199162149523126="` echo /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126 `" ) && sleep 0'
<nxos01> PUT /tmp/tmp0Im4Vi TO /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126/nxos_facts.py
<nxos01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126/ /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126/nxos_facts.py && sleep 0'
<nxos01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126/nxos_facts.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1491401561.48-199162149523126/" > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py", line 516, in <module>
    main()
  File "/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py", line 499, in main
    inst.populate()
  File "/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py", line 299, in populate
    self.facts['neighbors'] = self.populate_neighbors(data)
  File "/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py", line 320, in populate_neighbors
    if data.startswith('ERROR'):
AttributeError: 'dict' object has no attribute 'startswith'

fatal: [nxos01]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py\", line 516, in <module>\n    main()\n  File \"/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py\", line 499, in main\n    inst.populate()\n  File \"/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py\", line 299, in populate\n    self.facts['neighbors'] = self.populate_neighbors(data)\n  File \"/tmp/ansible_HvcZF9/ansible_module_nxos_facts.py\", line 320, in populate_neighbors\n    if data.startswith('ERROR'):\nAttributeError: 'dict' object has no attribute 'startswith'\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 0
}

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_facts.py b/lib/ansible/modules/network/nxos/nxos_facts.py
--- a/lib/ansible/modules/network/nxos/nxos_facts.py
+++ b/lib/ansible/modules/network/nxos/nxos_facts.py
@@ -218,7 +218,6 @@ def transform_iterable(self, iterable, keymap):
             yield self.transform_dict(item, keymap)
 
 
-
 class Default(FactsBase):
 
     VERSION_MAP = frozenset([
@@ -315,23 +314,38 @@ def populate_interfaces(self, data):
         return interfaces
 
     def populate_neighbors(self, data):
-        # if there are no neighbors the show command returns
-        # ERROR: No neighbour information
-        if data.startswith('ERROR'):
-            return dict()
-
-        lines = data.split('\n')
-        regex = re.compile('(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)')
-
         objects = dict()
+        if isinstance(data, str):
+            # if there are no neighbors the show command returns
+            # ERROR: No neighbour information
+            if data.startswith('ERROR'):
+                return dict()
+
+            lines = data.split('\n')
+            regex = re.compile('(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)')
+
+            for item in data.split('\n')[4:-1]:
+                match = regex.match(item)
+                if match:
+                    nbor = {'host': match.group(1), 'port': match.group(3)}
+                    if match.group(2) not in objects:
+                        objects[match.group(2)] = []
+                    objects[match.group(2)].append(nbor)
+
+        elif isinstance(data, dict):
+            data = data['TABLE_nbor']['ROW_nbor']
+            if isinstance(data, dict):
+                data = [data]
+
+            for item in data:
+                local_intf = item['l_port_id']
+                if local_intf not in objects:
+                    objects[local_intf] = list()
+                nbor = dict()
+                nbor['port'] = item['port_id']
+                nbor['host'] = item['chassis_id']
+                objects[local_intf].append(nbor)
 
-        for item in data.split('\n')[4:-1]:
-            match = regex.match(item)
-            if match:
-                nbor = {'host': match.group(1), 'port': match.group(3)}
-                if match.group(2) not in objects:
-                    objects[match.group(2)] = []
-                objects[match.group(2)].append(nbor)
         return objects
 
     def parse_ipv6_interfaces(self, data):


Test patch:
diff --git a/test/integration/targets/nxos_facts/tasks/nxapi.yaml b/test/integration/targets/nxos_facts/tasks/nxapi.yaml
--- a/test/integration/targets/nxos_facts/tasks/nxapi.yaml
+++ b/test/integration/targets/nxos_facts/tasks/nxapi.yaml
@@ -8,8 +8,21 @@
 - name: set test_items
   set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
 
+- name: enable nxapi
+  nxos_config:
+    lines:
+      - feature nxapi
+      - nxapi http port 80
+    provider: "{{ cli }}"
+
 - name: run test case
   include: "{{ test_case_to_run }}"
   with_items: "{{ test_items }}"
   loop_control:
     loop_var: test_case_to_run
+
+- name: disable nxapi
+  nxos_config:
+    lines:
+      - no feature nxapi
+    provider: "{{ cli }}"