Skip to content

fix to decode JSON from response to get_facts#826

Open
chidanandpujar wants to merge 1 commit into
Juniper:masterfrom
chidanandpujar:pyezconngetfacts
Open

fix to decode JSON from response to get_facts#826
chidanandpujar wants to merge 1 commit into
Juniper:masterfrom
chidanandpujar:pyezconngetfacts

Conversation

@chidanandpujar
Copy link
Copy Markdown
Collaborator

ROOT CAUSE & FIX SUMMARY

ROOT CAUSE

Ansible persistent connections use a JSON-RPC Unix socket. The connection plugin
process serializes return values via pickle.dumps(). When get_facts() returned
dict(self.dev.facts), the dict contained PyEZ junos.version_info namedtuple
objects which are not safely picklable. This caused the connection process to
crash without sending a socket reply. The caller received None from the socket,
which json.loads('None') could not parse, producing:

"Unable to decode JSON from response to get_facts(). Received 'None'."

Secondary bug: PR #816 partially fixed this using json.dumps(..., default=str),
but str() converted namedtuples to opaque strings like
"junos.version_info(major=(25, 4), type=R, minor=1, build=12)" instead of
structured dicts, breaking version_info and junos_info[re0]['object'].

FILES MODIFIED

  1. plugins/connection/pyez.py

    a) Added module-level helper before class Connection:

    def _serialize_fact(obj):
    if hasattr(obj, "_fields"): # namedtuple
    return dict(zip(obj._fields, obj))
    return str(obj)

    Converts junos.version_info namedtuples to plain dicts
    e.g. {"major": [25, 4], "type": "R", "minor": 1, "build": 12}
    Falls back to str() only for truly unknown types.

    b) Updated get_facts() method:

    @ensure_connect # guarantees device is connected
    def get_facts(self):
    facts = self.dev.facts
    if facts is None: # guard against None facts
    return {}
    return json.loads(json.dumps(dict(facts), default=_serialize_fact))

    Changes: added @ensure_connect, None guard, default=_serialize_fact
    (was: default=str, no decorator, no None guard)

  2. plugins/modules/facts.py

    Added None guard in get_facts_dict() pyez path:

    else:
    facts = junos_module.get_facts()
    if facts is None: # <-- added
    facts = {} # <-- added

    Prevents KeyError/TypeError if get_facts() returns None.

Before fix:

[WARNING]: Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.
[DEPRECATION WARNING]: Importing 'to_bytes' from 'ansible.module_utils._text' is deprecated. This feature will be removed from ansible-core version 2.24. Use ansible.module_utils.common.text.converters instead.
[ERROR]: Task failed: Module failed: Unable to decode JSON from response to get_facts(). Received 'None'.

Task failed: Module failed.
Origin: /root/ansible_release_final/ansible-junos-stdlib/tests/pb.juniper_junos_facts.yml:6:7

4   gather_facts: false
5   tasks:
6     - name: "TEST 1 - Gather Facts"
        ^ column 7

<<< caused by >>>

Unable to decode JSON from response to get_facts(). Received 'None'.

fatal: [pyez_connection_testcases]: FAILED! => {
    "changed": false,
    "msg": "Task failed: Module failed: Unable to decode JSON from response to get_facts(). Received 'None'."
}
...ignoring

TASK [Check TEST 1] ******************************************************************************************************************
task path: /root/ansible_release_final/ansible-junos-stdlib/tests/pb.juniper_junos_facts.yml:12
[ERROR]: Task failed: Action failed: Assertion failed
Origin: /root/ansible_release_final/ansible-junos-stdlib/tests/pb.juniper_junos_facts.yml:12:7

10
11
12     - name: Check TEST 1
         ^ column 7

fatal: [pyez_connection_testcases]: FAILED! => {
    "assertion": "test1.facts.hostname is defined and test1.facts.hostname | length > 0",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

PLAY RECAP ***************************************************************************************************************************
pyez_connection_testcases  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=1 

After fix:

(relfinal) root@masterhost:~/ansible_release_final/ansible-junos-stdlib/tests# ansible-playbook -i inventory pb.juniper_junos_facts.yml 

PLAY [Test juniper.device.facts module] **********************************************************************************************

TASK [TEST 1 - Gather Facts] *********************************************************************************************************
[WARNING]: Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.
[DEPRECATION WARNING]: Importing 'to_bytes' from 'ansible.module_utils._text' is deprecated. This feature will be removed from ansible-core version 2.24. Use ansible.module_utils.common.text.converters instead.
ok: [pyez_connection_testcases]

TASK [Check TEST 1] ******************************************************************************************************************
ok: [pyez_connection_testcases] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [TEST 2 - get facts in xml format] **********************************************************************************************
ok: [pyez_connection_testcases]

TASK [Check TEST 2] ******************************************************************************************************************
ok: [pyez_connection_testcases] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP ***************************************************************************************************************************
pyez_connection_testcases  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant