From 74c85faaf0cd5549e0e887672986a3b8f30a4c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Jeanneret?= Date: Tue, 19 Jan 2021 11:41:30 +0100 Subject: [PATCH] Conditionally use python instead of cURL The healthcheck_curl has always been a bit bonky with the proxies management, especially the "no_proxy" environment variable: since there isn't any real RFC describing the content of this variable, there isn't any unified handling. This leads to situation where an Operator puts some CIDR notation inside this variable, while curl doesn't handle it. This change adds a new python script. Since it uses python-requests, it will have a proper support for the CIDR notation[1]. It will be called if and only if either NO_PROXY or no_proxy environment variables are set and non-empty. One can also force the script usage by passing HEALTHCHECK_CURL_PY=1, such as: podman exec -e "HEALTHCHECK_CURL_PY=1" container /healthchecks/you-healthcheck The output is 100% iso-compatible, and if someone wants to know what healthcheck is used, they can check the User-Agent on the target logs. This change is motivated by, at least, the following existing issues: https://bugzilla.redhat.com/show_bug.cgi?id=1837458 https://bugzilla.redhat.com/show_bug.cgi?id=1883657 [1] https://github.com/psf/requests/blob/589c4547338b592b1fb77c65663d8aa6fbb7e38b/requests/utils.py#L663-L684 Note: stable/train patch has a different shebang than the original one due to the CentOS7 support: we don't push any python3 dependencies in the containers for that OS release. Depends-On: https://review.rdoproject.org/r/#/c/31813/ Change-Id: If56cd0fa986ef193d036b73e0ab84a88d59147e3 (cherry picked from commit 2185d9a8599f4efb79fe38b188bc48e931eba625) --- healthcheck/common.sh | 11 ++++++++- healthcheck/http-healthcheck.py | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100755 healthcheck/http-healthcheck.py diff --git a/healthcheck/common.sh b/healthcheck/common.sh index 33657eaf3..2ca9c6aac 100755 --- a/healthcheck/common.sh +++ b/healthcheck/common.sh @@ -9,6 +9,7 @@ else fi : ${HEALTHCHECK_CURL_MAX_TIME:=10} : ${HEALTHCHECK_CURL_USER_AGENT:=curl-healthcheck} +: ${HEALTHCHECK_CURL_PY_USER_AGENT:=pyrequests-healthcheck} : ${HEALTHCHECK_CURL_WRITE_OUT:='\n%{http_code} %{remote_ip}:%{remote_port} %{time_total} seconds\n'} : ${HEALTHCHECK_CURL_OUTPUT:='/dev/null'} @@ -30,11 +31,19 @@ healthcheck_curl () { return 1 fi export NSS_SDB_USE_CACHE=no - curl -g -k -q -s -S --fail -o "${HEALTHCHECK_CURL_OUTPUT}" \ + if [ -n "${HEALTHCHECK_CURL_PY+x}" ] || [ -n "${no_proxy+x}" ] || [ -n "${NO_PROXY+x}" ]; then + ${HEALTHCHECK_SCRIPTS:-/usr/share/openstack-tripleo-common/healthcheck}/http-healthcheck.py \ + --max-time "${HEALTHCHECK_CURL_MAX_TIME}" \ + --user-agent "${HEALTHCHECK_CURL_PY_USER_AGENT}" \ + --write-out "${HEALTHCHECK_CURL_WRITE_OUT}" \ + "$@" || return 1 + else + curl -g -k -q -s -S --fail -o "${HEALTHCHECK_CURL_OUTPUT}" \ --max-time "${HEALTHCHECK_CURL_MAX_TIME}" \ --user-agent "${HEALTHCHECK_CURL_USER_AGENT}" \ --write-out "${HEALTHCHECK_CURL_WRITE_OUT}" \ "$@" || return 1 + fi } healthcheck_port () { diff --git a/healthcheck/http-healthcheck.py b/healthcheck/http-healthcheck.py new file mode 100755 index 000000000..00076de88 --- /dev/null +++ b/healthcheck/http-healthcheck.py @@ -0,0 +1,44 @@ +#!/usr/bin/python2 +import argparse +import os +import requests + +default_output = ("\n%(http_code)s %(remote_ip)s:%(remote_port)s " + "%(time_total)s seconds\n") + +parser = argparse.ArgumentParser(description='Check remote HTTP') +parser.add_argument('uri', metavar='URI', type=str, nargs=1, + help='Remote URI to check') +parser.add_argument('--max-time', type=int, default=10, + help=('Maximum time in seconds that you allow the' + ' whole operation to take.') + ) +parser.add_argument('--user-agent', type=str, default='pyrequests-healthcheck', + help=('Specify the User-Agent string to send to the' + ' HTTP server.') + ) +parser.add_argument('--write-out', type=str, default=default_output, + help=('Display information on stdout after a completed' + ' transfer.') + ) + +args = parser.parse_args() +uri = args.uri[0] +output = args.write_out.replace('%{', '%(').replace('}', ')s') \ + .replace('\\n', os.linesep) + +headers = {'User-Agent': args.user_agent} +with requests.get(uri, headers=headers, timeout=args.max_time, + allow_redirects=True, stream=True, verify=False) as req: + r_ip, r_port = req.raw._original_response.fp.raw._sock.getpeername() + resp = {'http_code': req.status_code, + 'remote_ip': r_ip, + 'remote_port': r_port, + 'time_total': req.elapsed.total_seconds() + } + try: + print(output % resp) + except KeyError: + print(default_output % resp) + except ValueError: + print(default_output % resp)