Repository: ansible/ansible

Issue:
snmp_facts fails with "AttributeError: 'str' object has no attribute 'decode'" on python3
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
modules/network/snmp_facts

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
default

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
Tested on Arch linux, but should be N/A

##### SUMMARY
<!--- Explain the problem briefly -->
snmp_facts has the following code call:
```
  143     if hexstring[:2] == "0x":                                                                            
  144         return hexstring[2:].decode("hex")  
```
On python3 decode() does not exist for strings, so this specific code needs to be altered to support both python 2 and 3.

As far as I can tell this will only be triggered when fetching facts from systems that hex encode the output of sysdescr, most notable from my brief testing is Cisco IOS and Cisco IOS XR.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: ios
  connection: local
  vars:
  tasks:
    - name: SNMP Call 
      snmp_facts: host={{ inventory_hostname }} version=v2c community=public
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Facts returned
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
Using /etc/ansible/ansible.cfg as config file

PLAYBOOK: snmp.yml ********************************************************************************************
1 plays in snmp.yml

PLAY [Identify network operating system using SNMP] ***********************************************************

TASK [Gathering Facts] ****************************************************************************************
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/setup.py
<xxx.xxx.xxx.xxx> ESTABLISH LOCAL CONNECTION FOR USER: tjuberg
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c 'echo ~ && sleep 0'
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677 `" && echo ansible-tmp-1487590039.5-43328809108677="` echo /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677 `" ) && sleep 0'
<xxx.xxx.xxx.xxx> PUT /tmp/tjuberg/tmpsBN1eW TO /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677/setup.py
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c 'chmod u+x /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677/ /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677/setup.py && sleep 0'
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c '/usr/bin/python /home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677/setup.py; rm -rf "/home/tjuberg/.ansible/tmp/ansible-tmp-1487590039.5-43328809108677/" > /dev/null 2>&1 && sleep 0'
ok: [xxx.xxx.xxx.xxx]
META: ran handlers

TASK [SNMP Call] **********************************************************************************************
task path: /home/tjuberg/work/gitlab/bachelor_ansible/snmp.yml:8
Using module file /usr/lib/python2.7/site-packages/ansible/modules/network/snmp_facts.py
<xxx.xxx.xxx.xxx> ESTABLISH LOCAL CONNECTION FOR USER: tjuberg
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c 'echo ~ && sleep 0'
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094 `" && echo ansible-tmp-1487590040.13-259195315274094="` echo /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094 `" ) && sleep 0'
<xxx.xxx.xxx.xxx> PUT /tmp/tjuberg/tmpcQ5K2o TO /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094/snmp_facts.py
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c 'chmod u+x /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094/ /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094/snmp_facts.py && sleep 0'
<xxx.xxx.xxx.xxx> EXEC /bin/sh -c '/usr/bin/python /home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094/snmp_facts.py; rm -rf "/home/tjuberg/.ansible/tmp/ansible-tmp-1487590040.13-259195315274094/" > /dev/null 2>&1 && sleep 0'
 [WARNING]: Removed unexpected internal key in module return: _ansible_parsed = False

An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py", line 375, in <module>
    main()
  File "/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py", line 270, in main
    results['ansible_sysdescr'] = decode_hex(current_val)
  File "/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py", line 144, in decode_hex
    return hexstring[2:].decode("hex")
AttributeError: 'str' object has no attribute 'decode'

fatal: [xxx.xxx.xxx.xxx]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py\", line 375, in <module>\n    main()\n  File \"/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py\", line 270, in main\n    results['ansible_sysdescr'] = decode_hex(current_val)\n  File \"/tmp/tjuberg/ansible_i83smb6y/ansible_module_snmp_facts.py\", line 144, in decode_hex\n    return hexstring[2:].decode(\"hex\")\nAttributeError: 'str' object has no attribute 'decode'\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 0
}

msg: MODULE FAILURE
	to retry, use: --limit @/home/tjuberg/work/gitlab/bachelor_ansible/snmp.retry

PLAY RECAP ****************************************************************************************************
xxx.xxx.xxx.xxx             : ok=1    changed=0    unreachable=0    failed=1   

```



Discussion:
cc @ogenstad
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/network/snmp_facts.py b/lib/ansible/modules/network/snmp_facts.py
--- a/lib/ansible/modules/network/snmp_facts.py
+++ b/lib/ansible/modules/network/snmp_facts.py
@@ -95,7 +95,11 @@
   delegate_to: localhost
 '''
 
+import binascii
+
 from ansible.module_utils.basic import *
+from ansible.module_utils._text import to_text
+
 from collections import defaultdict
 
 try:
@@ -141,7 +145,7 @@ def decode_hex(hexstring):
     if len(hexstring) < 3:
         return hexstring
     if hexstring[:2] == "0x":
-        return hexstring[2:].decode("hex")
+        return to_text(binascii.unhexlify(hexstring[2:]))
     else:
         return hexstring
 


Test patch: