Skip to content
Snippets Groups Projects
test_nginx_vhosts.py 7.5 KiB
Newer Older
Stéphane Diemer's avatar
Stéphane Diemer committed
#!/usr/bin/env python3
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed

"""
Tests that all webserver services (vhosts) are available and reachable.
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
"""

Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
from pathlib import Path
Stéphane Diemer's avatar
Stéphane Diemer committed
import re
import requests
Stéphane Diemer's avatar
Stéphane Diemer committed
import sys
try:
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
    requests.packages.urllib3.disable_warnings()
Stéphane Diemer's avatar
Stéphane Diemer committed

Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
sys.path.append(str(Path(__file__).parents[1].resolve()))

# pylint: disable=wrong-import-position
from envsetup import utils as u  # noqa: E402

Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
"""
This script checks for all enabled vhosts in Nginx conf that:
* The response status code is 200, 401 or 403.
* The host is resolved as 127.0.0.1.
* The Wowza response is correct on /streaming/ (only for mediaserver vhosts).
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
"""
Stéphane Diemer's avatar
Stéphane Diemer committed

Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
def get_configs(path: str) -> list:
    configs_dir = Path(path)
    configs = [c.resolve() for c in configs_dir.glob("*.conf")]

    return configs


def get_vhosts(config: Path) -> list:
    # remove comments and blank lines
    sanitize = re.compile(r"(?:\s*#\s*.*)|(?:^\s*)", re.M)
    # capture server blocks
    servers = re.compile(r"^server\s+{(?:\s*(?!server\s{).)+", re.M)

    with open(config) as config_fo:
        config_content = sanitize.sub(r"", config_fo.read())
        vhosts = servers.findall(config_content)

    return vhosts


def get_hostnames(vhost: str) -> list:
    # extract hostname(s) from server_name values
    server_names = re.compile(r"^\s*server_name\s+(.*);$")

    hostnames = []
    for line in vhost.splitlines():
        if server_names.match(line):
            hostnames.extend(server_names.match(line)[1].split())

    return hostnames


def get_ports(vhost: str) -> list:
    # extract port(s) from listen values
    listens = re.compile(r"^\s*listen\s+(?:.*:)?(\d+)\s*(ssl)?.*;$")

    ports = []
    for line in vhost.splitlines():
        if listens.match(line):
            ports.append(
                (listens.match(line)[1], "https" if listens.match(line)[2] else "http")
            )

    return ports


Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
def test_vhost(
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    ports_info=None,
    domains=None,
    resolution_ignored=None,
    celerity_conf="",
    nginx_file=None,
    wowza_dir=None,
    tested=0,
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
):
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    name = nginx_file.name
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    for port, proto in ports_info or [(80, False)]:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        for domain in domains or ["localhost"]:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            url = "%s://%s:%s" % (proto, domain, port)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            u.info("testing url '%s' from %s" % (url, name))
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            if name.startswith("mediaserver") and not tested:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                if not re.search(r"https?://%s" % domain, celerity_conf):
                    u.warning("url '%s' not found in celerity conf" % url)
                    warnings += 1
            # test domain IP
            ip_error = None
            ip_warning = None
            try:
                ip = socket.gethostbyname(domain)
            except Exception as e:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                ip_error = "domain: not resolved %s" % e
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                if ip != "127.0.0.1":
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    ip_warning = "domain: resolve to %s instead of 127.0.0.1" % ip
            if ip_error:
                if resolution_ignored and domain in resolution_ignored:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.info("%s (ignored)" % ip_error)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.error(ip_error)
            elif ip_warning:
                if resolution_ignored and domain in resolution_ignored:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.info("%s (ignored)" % ip_warning)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.warning(ip_warning)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                u.success("domain: resolve to 127.0.0.1")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                req = requests.get(
                    url, verify=False, proxies={"http": "", "https": ""}, timeout=30
                )
                req_time = int(1000 * req.elapsed.total_seconds())
            except Exception as e:
                code = str(e)
                req_time = 0
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            if (
                domain != "localhost"
                and code not in (200, 401, 403)
                or domain == "localhost"
                and code not in (200, 401, 403, 404)
            ):
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                u.error("status: %s, %sms" % (code, req_time))
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.warning("status: %s, %sms" % (code, req_time))
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.success("status: %s, %sms" % (code, req_time))
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                if "mediaserver" in name and wowza_dir:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                        req = requests.get(
                            url + "/streaming/",
                            verify=False,
                            proxies={"http": "", "https": ""},
                            timeout=30,
                        )
                        req_time = int(1000 * req.elapsed.total_seconds())
                    except Exception as e:
                        code = str(e)
                        req_time = 0
                    else:
                        code = req.status_code
                    if code != 200:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                        u.error("streaming: %s, %sms" % (code, req_time))
                        req_error = True
                    elif req_time > 10000:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                        u.warning("streaming: %s, %sms" % (code, req_time))
                        warnings += 1
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                        u.success("streaming: %s, %sms" % (code, req_time))
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            tested += 1

            if ip_warning:
                warnings += 1

            if ip_error or req_error:
                errors += 1
    return tested, warnings, errors


Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
def main():
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    print("Check that nginx vhosts are well configured:")
    # check that Nginx dir exists
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    nginx_dir = "/etc/nginx/sites-enabled"
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    if not Path(nginx_dir).exists():
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        u.error("nginx dir does not exists ('%s')." % nginx_dir)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        exit(2)

    # check that Wowza is installed
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    wowza_dir = "/usr/local/WowzaStreamingEngine"
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    if not Path(wowza_dir).exists():
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        u.info("wowza is not installed ('%s' does not exist)." % wowza_dir)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        u.info("wowza is installed, /streaming/ will be tested on mediaserver vhosts.")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    conf = u.load_conf()
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    celerity_conf = ""
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    if Path("/etc/celerity/config.py").exists():
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        with open("/etc/celerity/config.py", "r") as fo:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    resolution_ignored = conf.get("TESTER_VHOST_RESOLUTION_IGNORED", "").split(",")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    nginx_confs = get_configs(nginx_dir)
    for nginx_conf in nginx_confs:
        tested = 0
        vhosts = get_vhosts(nginx_conf)
        for vhost in vhosts:
            hostnames = get_hostnames(vhost)
            ports = get_ports(vhost)
            t, w, e = test_vhost(
                ports,
                hostnames,
                resolution_ignored,
                celerity_conf,
                nginx_conf,
                wowza_dir,
                tested,
            )
            tested += t
            warnings += w
            errors += e
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        exit(1)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        exit(3)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        u.error("no url found in nginx sites-enabled dir")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        exit(1)
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed


if __name__ == "__main__":
    main()