Skip to content
Snippets Groups Projects
test_nginx_vhosts.py 7.11 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
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)

Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
    with open(str(config)) as config_fo:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
        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):
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            hostnames.extend(server_names.match(line).group(1).split())
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed

    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(
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                (listens.match(line).group(1), "https" if listens.match(line).group(2) else "http")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            )

    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 = f"{proto}://{domain}:{port}"
            u.info(f"- testing url '{url}' from {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):
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.warning(f"url '{url}' not found in celerity conf")
                    warnings += 1
            # test domain IP
            ip_error = None
            ip_warning = None
            try:
                ip = socket.gethostbyname(domain)
            except Exception as e:
                ip_error = f"{domain} not resolved: {e}"
                if ip != "127.0.0.1" and ip != "127.0.1.1":
                    ip_warning = f"{domain} resolve to {ip} instead of 127.0.0.1"
            if ip_error:
                if resolution_ignored and domain in resolution_ignored:
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.info(f"{ip_error} (ignored)")
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(f"{ip_warning} (ignored)")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                    u.warning(ip_warning)
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)
            ):
                u.error(f"{domain} status: {code}, {req_time}ms")
                    u.warning(f"{domain} status: {code}, {req_time}ms")
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:
                        u.error(f"{domain} streaming: {code}, {req_time}ms")
                        req_error = True
                    elif req_time > 10000:
                        u.warning(f"{domain} streaming: {code}, {req_time}ms")
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
                        warnings += 1
Nicolas KAROLAK's avatar
Nicolas KAROLAK committed
            tested += 1

            if ip_warning:
                warnings += 1

            if ip_error or req_error:
                errors += 1
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.info(f"nginx dir does not exists ('{nginx_dir}'), test skipped.")
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():
        wowza_dir = None

    # get envsetup conf
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()