Ansible - HTTP Error 502: Bad Gateway when using script with XMLRPC

Viewed 551

I have this script, that connects to service via XMLRPC, like:

import odoorpc
odoo = odoorpc.ODOO(host, protocol=protocol, port=port)

Task from yaml file:

- name: Change Odoo admin user password
  ansible.builtin.script: "change-user-password \
    {{ secrets_odoo_init_user_passwords_file }} --ignore-failed-login \
    --host {{ ansible_host }} --port 443 --protocol jsonrpc+ssl"
  become: true
  become_user: "{{ app_user }}"

Call looks like this (from ansible logs):

<my-host> SSH: EXEC ssh -o ControlMaster=auto -o ControlPersist=30m -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/tmp/ansible-ssh-%h-%p-%r -tt my-host '/bin/sh -c '"'"'sudo -H -S -n  -u odoo /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-lsrscdzqfzfliptrdleqjxcwaseuaggn ;  /var/tmp/ansible-tmp-1635367335.375884-2165879-123865031409885/change-user-password /opt/odoo/.secrets/odoo_init_user_passwords_file --ignore-failed-login --host my-host --port 443 --protocol jsonrpc+ssl'"'"'"'"'"'"'"'"' && sleep 0'"'"''

The full traceback is:

NoneType: None
fatal: [dev]: FAILED! => {
    "changed": true,
    "msg": "non-zero return code",
    "rc": 1,
    "stderr": "Shared connection to my-host closed.\r\n",
    "stderr_lines": [
        "Shared connection to my-host closed."
    ],
    "stdout": "Traceback (most recent call last):\r\n  File \"/var/tmp/ansible-tmp-1635367335.375884-2165879-123865031409885/change-user-password\", line 109, in <module>\r\n    odoo = login(\r\n  File \"/var/tmp/ansible-tmp-1635367335.375884-2165879-123865031409885/change-user-password\", line 75, in login\r\n    odoo = odoorpc.ODOO(host, protocol=protocol, port=port)\r\n  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/odoo.py\", line 95, in __init__\r\n    self._connector = rpc.PROTOCOLS[protocol](\r\n  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 288, in __init__\r\n    super(ConnectorJSONRPCSSL, self).__init__(\r\n  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 211, in __init__\r\n    self._proxy_json, self._proxy_http = self._get_proxies()\r\n  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 235, in _get_proxies\r\n    result = proxy_json('/web/webclient/version_info')['result']\r\n  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/jsonrpclib.py\", line 113, in __call__\r\n    response = self._opener.open(request, timeout=self._timeout)\r\n  File \"/usr/lib/python3.9/urllib/request.py\", line 523, in open\r\n    response = meth(req, response)\r\n  File \"/usr/lib/python3.9/urllib/request.py\", line 632, in http_response\r\n    response = self.parent.error(\r\n  File \"/usr/lib/python3.9/urllib/request.py\", line 561, in error\r\n    return self._call_chain(*args)\r\n  File \"/usr/lib/python3.9/urllib/request.py\", line 494, in _call_chain\r\n    result = func(*args)\r\n  File \"/usr/lib/python3.9/urllib/request.py\", line 641, in http_error_default\r\n    raise HTTPError(req.full_url, code, msg, hdrs, fp)\r\nurllib.error.HTTPError: HTTP Error 502: Bad Gateway\r\n",
    "stdout_lines": [
        "Traceback (most recent call last):",
        "  File \"/var/tmp/ansible-tmp-1635367335.375884-2165879-123865031409885/change-user-password\", line 109, in <module>",
        "    odoo = login(",
        "  File \"/var/tmp/ansible-tmp-1635367335.375884-2165879-123865031409885/change-user-password\", line 75, in login",
        "    odoo = odoorpc.ODOO(host, protocol=protocol, port=port)",
        "  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/odoo.py\", line 95, in __init__",
        "    self._connector = rpc.PROTOCOLS[protocol](",
        "  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 288, in __init__",
        "    super(ConnectorJSONRPCSSL, self).__init__(",
        "  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 211, in __init__",
        "    self._proxy_json, self._proxy_http = self._get_proxies()",
        "  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/__init__.py\", line 235, in _get_proxies",
        "    result = proxy_json('/web/webclient/version_info')['result']",
        "  File \"/opt/odoo/.local/lib/python3.9/site-packages/odoorpc/rpc/jsonrpclib.py\", line 113, in __call__",
        "    response = self._opener.open(request, timeout=self._timeout)",
        "  File \"/usr/lib/python3.9/urllib/request.py\", line 523, in open",
        "    response = meth(req, response)",
        "  File \"/usr/lib/python3.9/urllib/request.py\", line 632, in http_response",
        "    response = self.parent.error(",
        "  File \"/usr/lib/python3.9/urllib/request.py\", line 561, in error",
        "    return self._call_chain(*args)",
        "  File \"/usr/lib/python3.9/urllib/request.py\", line 494, in _call_chain",
        "    result = func(*args)",
        "  File \"/usr/lib/python3.9/urllib/request.py\", line 641, in http_error_default",
        "    raise HTTPError(req.full_url, code, msg, hdrs, fp)",
        "urllib.error.HTTPError: HTTP Error 502: Bad Gateway"
    ]
}

I read that people workaround this error by using headers={'User-Agent': 'Mozilla/5.0'}. But if I use script module via ansible, how can I specify such header?

The thing is, I only get this error if I run script via ansible. I tried running that script explicitly from my machine and from target machine. In those cases, it works fine.

ansible [core 2.11.6] 
  config file = /home/oerp/.ansible.cfg
  configured module search path = ['/home/oerp/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/oerp/.local/lib/python3.8/site-packages/ansible
  ansible collection location = /home/oerp/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/oerp/.local/bin/ansible
  python version = 3.8.10 (default, Sep 28 2021, 16:10:42) [GCC 9.3.0]
  jinja version = 3.0.1
  libyaml = True

Update

Turn out the problem when XMLPRC call is made, service is not yet running, thus the error.

So I have to use RPC only when service is already running.

I tried using wait_for like this (note, my Odoo service port is not exposed via compose, its only accessible through docker network by nginx):

- name: Save Odoo container status
  set_fact:
    odoo_ip: "{{ docker_compose_output.services.odoo.app_odoo_1.networks.app_backend.IPAddress }}"

- name: Wait for IP to be active
  wait_for:
    host: "{{ odoo_ip }}"
    delay: 1
    state: drained

I do get this error:

The full traceback is:
Traceback (most recent call last):
  File "/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py", line 100, in <module>
    _ansiballz_main()
  File "/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py", line 92, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py", line 40, in invoke_module
    runpy.run_module(mod_name='ansible.modules.wait_for', init_globals=dict(_module_fqn='ansible.modules.wait_for', _modlib_path=modlib_path),
  File "/usr/lib/python3.9/runpy.py", line 210, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.9/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py", line 674, in <module>
  File "/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py", line 657, in main
  File "/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py", line 356, in __init__
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
fatal: [dev]: FAILED! => {
    "changed": false,
    "module_stderr": "Shared connection to my-host closed.\r\n",
    "module_stdout": "Traceback (most recent call last):\r\n  File \"/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py\", line 100, in <module>\r\n    _ansiballz_main()\r\n  File \"/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py\", line 92, in _ansiballz_main\r\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n  File \"/root/.ansible/tmp/ansible-tmp-1635403378.5625799-2174550-176521610555803/AnsiballZ_wait_for.py\", line 40, in invoke_module\r\n    runpy.run_module(mod_name='ansible.modules.wait_for', init_globals=dict(_module_fqn='ansible.modules.wait_for', _modlib_path=modlib_path),\r\n  File \"/usr/lib/python3.9/runpy.py\", line 210, in run_module\r\n    return _run_module_code(code, init_globals, run_name, mod_spec)\r\n  File \"/usr/lib/python3.9/runpy.py\", line 97, in _run_module_code\r\n    _run_code(code, mod_globals, init_globals,\r\n  File \"/usr/lib/python3.9/runpy.py\", line 87, in _run_code\r\n    exec(code, run_globals)\r\n  File \"/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py\", line 674, in <module>\r\n  File \"/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py\", line 657, in main\r\n  File \"/tmp/ansible_wait_for_payload_pic_t21b/ansible_wait_for_payload.zip/ansible/modules/wait_for.py\", line 356, in __init__\r\nTypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'\r\n",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}

Its unclear what argument it is about.

1 Answers

Turns out, the problem was that service (container) I was trying to connect with RPC was not yet running and that was causing bad gateway problem.

In my playbook, container is stopped and then started and just after initiating start, it tries to use RPC, which would fail (as my reverse proxy, which is nginx, would not yet see odoo service).

So simple fix is this:

- name: Pause for 3 seconds for Odoo service to load
  pause:
    seconds: 3

Though its not ideal as I can't be sure that I would need 3 seconds. It might be too long or too fast.

I tried using wait_for, but got some confusing errors. I think wait_for is better approach as it could react better than simple pause.

Update

Better way would be to use wait_for, so we don't need to specify specific delay time. Like (this way it waits till service is ready to be used):

- name: Save Odoo container status
  set_fact:
    odoo_ip: "{{ docker_compose_output.services.odoo.app_odoo_1.networks\
      .app_backend.IPAddress }}"

- name: Wait for IP to be active
  wait_for:
    host: "{{ odoo_ip }}"
    port: 8069
    state: started
Related