diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:55:53 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:55:53 +0000 |
commit | 3d0386f27ca66379acf50199e1d1298386eeeeb8 (patch) | |
tree | f87bd4a126b3a843858eb447e8fd5893c3ee3882 /ci | |
parent | Initial commit. (diff) | |
download | knot-resolver-upstream.tar.xz knot-resolver-upstream.zip |
Adding upstream version 3.2.1.upstream/3.2.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ci')
-rw-r--r-- | ci/README.md | 21 | ||||
-rw-r--r-- | ci/debian-stable/Dockerfile | 92 | ||||
-rw-r--r-- | ci/debian-unstable/Dockerfile | 43 | ||||
-rw-r--r-- | ci/debian-unstable/README | 1 | ||||
-rw-r--r-- | ci/fedora/Dockerfile | 22 | ||||
-rwxr-xr-x | ci/pytests/lint.sh | 16 | ||||
-rwxr-xr-x | ci/pytests/pylint-run.sh | 9 | ||||
-rwxr-xr-x | ci/pytests/run-extended.sh | 5 | ||||
-rwxr-xr-x | ci/pytests/run.sh | 3 | ||||
-rw-r--r-- | ci/respdiff/kresd.config | 24 | ||||
-rw-r--r-- | ci/respdiff/respdiff-tcp.conf | 50 | ||||
-rw-r--r-- | ci/respdiff/respdiff-tls.conf | 50 | ||||
-rw-r--r-- | ci/respdiff/respdiff-udp.conf | 50 | ||||
-rwxr-xr-x | ci/respdiff/restart-bind.sh | 2 | ||||
-rwxr-xr-x | ci/respdiff/restart-kresd.sh | 11 | ||||
-rwxr-xr-x | ci/respdiff/restart-unbound.sh | 2 | ||||
-rwxr-xr-x | ci/respdiff/run-respdiff-tests.sh | 25 | ||||
-rwxr-xr-x | ci/respdiff/start-resolvers.sh | 11 | ||||
-rwxr-xr-x | ci/travis.py | 47 |
19 files changed, 484 insertions, 0 deletions
diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000..be6d75d --- /dev/null +++ b/ci/README.md @@ -0,0 +1,21 @@ +Docker Build +------------ + +* debian-stable / debian-unstable + +``` +$ export KNOT_BRANCH=2.7 +$ docker build -t registry.labs.nic.cz/knot/knot-resolver/ci/debian-stable:knot-$KNOT_BRANCH --build-arg KNOT_BRANCH=$KNOT_BRANCH debian-stable +$ docker build -t registry.labs.nic.cz/knot/knot-resolver/ci/debian-unstable:knot-$KNOT_BRANCH --build-arg KNOT_BRANCH=$KNOT_BRANCH debian-unstable + +$ docker login registry.labs.nic.cz +$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/debian-stable:knot-$KNOT_BRANCH +$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/debian-unstable:knot-$KNOT_BRANCH +``` + +* fedora + +``` +$ docker build -t registry.labs.nic.cz/knot/knot-resolver/ci/fedora fedora +$ docker push registry.labs.nic.cz/knot/knot-resolver/ci/fedora +``` diff --git a/ci/debian-stable/Dockerfile b/ci/debian-stable/Dockerfile new file mode 100644 index 0000000..ab9b828 --- /dev/null +++ b/ci/debian-stable/Dockerfile @@ -0,0 +1,92 @@ +FROM debian:stable +MAINTAINER Knot Resolver <knot-resolver@labs.nic.cz> +ARG KNOT_BRANCH=master + +WORKDIR /root +CMD ["/bin/bash"] + +RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list + +# generic cleanup +RUN apt-get update -qq +RUN apt-get upgrade -y -qqq + +# Knot and Knot Resolver dependecies +RUN apt-get -t stretch-backports install -y git +RUN apt-get install -y -qqq make cmake pkg-config build-essential bsdmainutils libtool autoconf liburcu-dev libgnutls28-dev libedit-dev liblmdb-dev libcap-ng-dev libsystemd-dev libidn11-dev protobuf-c-compiler libfstrm-dev libuv1-dev libcmocka-dev libluajit-5.1-dev lua-sec lua-socket lua-http +# documentation dependecies +RUN apt-get install -y -qqq doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme + +# Python packags required for Deckard CI +# Python: grab latest versions from PyPi +# (dnspython and Augeas binding in Debian packages are slow and buggy) +RUN apt-get install -y -qqq python3-pip wget augeas-tools +RUN pip3 install --upgrade pip +RUN pip3 install pylint +RUN pip3 install pep8 +RUN pip3 install pytest-xdist +# tests/pytest dependencies +RUN pip3 install dnspython jinja2 pytest pytest-html pytest-xdist + +# C dependencies for python-augeas +RUN apt-get install -y -qqq libaugeas-dev libffi-dev +# Python dependencies for Deckard +RUN wget https://gitlab.labs.nic.cz/knot/deckard/raw/master/requirements.txt -O /tmp/deckard-req.txt +RUN pip3 install -r /tmp/deckard-req.txt + +# build and install latest version of Knot DNS +# (kresd depends on libknot and libdnssec) +RUN git clone --depth=1 --branch=$KNOT_BRANCH https://gitlab.labs.nic.cz/knot/knot-dns.git /tmp/knot +WORKDIR /tmp/knot +RUN pwd +RUN autoreconf -if +RUN ./configure --prefix=/usr +RUN make +RUN make install +RUN ldconfig + +# Valgrind for kresd CI +RUN apt-get install valgrind -y -qqq +RUN wget https://raw.githubusercontent.com/LuaJIT/LuaJIT/v2.0.4/src/lj.supp -O /lj.supp +# TODO: rebuild LuaJIT with Valgrind support + +# Lua lint for kresd CI +RUN apt-get install luarocks -y -qqq +RUN luarocks install luacheck + +# respdiff for kresd CI +RUN apt-get install lmdb-utils -y -qqq +RUN git clone --depth=1 https://gitlab.labs.nic.cz/knot/respdiff /var/opt/respdiff +RUN pip3 install -r /var/opt/respdiff/requirements.txt + +# Python static analysis for respdiff +RUN pip3 install mypy +RUN pip3 install flake8 + +# Python requests for CI scripts +RUN pip3 install requests + +# Unbound for respdiff +RUN apt-get install unbound unbound-anchor -y -qqq +RUN printf "server:\n interface: 127.0.0.1@53535\n use-syslog: yes\n do-ip6: no\nremote-control:\n control-enable: no\n" >> /etc/unbound/unbound.conf + +# BIND for respdiff +RUN apt-get install bind9 -y -qqq +RUN printf '\nOPTIONS="-4 $OPTIONS"' >> /etc/default/bind9 +RUN printf 'options {\n directory "/var/cache/bind";\n listen-on port 53533 { 127.0.0.1; };\n listen-on-v6 port 53533 { ::1; };\n};\n' > /etc/bind/named.conf.options + +# PowerDNS Recursor for Deckard CI +RUN apt-get install pdns-recursor -y -qqq + +# code coverage +RUN apt-get install -y -qqq lcov +RUN luarocks install luacov + +# LuaJIT binary for stand-alone scripting +RUN apt-get install -y -qqq luajit + +# OpenBuildService CLI tool +RUN apt-get install -y osc + +# curl (API) +RUN apt-get install -y curl diff --git a/ci/debian-unstable/Dockerfile b/ci/debian-unstable/Dockerfile new file mode 100644 index 0000000..19315d9 --- /dev/null +++ b/ci/debian-unstable/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:unstable +MAINTAINER Knot Resolver <knot-resolver@labs.nic.cz> +ARG KNOT_BRANCH=2.7 + +WORKDIR /root +CMD ["/bin/bash"] + +# generic cleanup +RUN apt-get update -qq +RUN apt-get upgrade -y -qqq + +# Knot and Knot Resolver dependecies +RUN apt-get install -y -qqq make cmake pkg-config git build-essential bsdmainutils libtool autoconf liburcu-dev libgnutls28-dev libedit-dev liblmdb-dev libcap-ng-dev libsystemd-dev libidn11-dev protobuf-c-compiler libfstrm-dev libuv1-dev libcmocka-dev libluajit-5.1-dev lua-sec lua-socket lua-http + +# build and install latest version of Knot DNS +# (kresd depends on libknot and libdnssec) +RUN git clone --depth=1 --branch=$KNOT_BRANCH https://gitlab.labs.nic.cz/knot/knot-dns.git /tmp/knot +WORKDIR /tmp/knot +RUN pwd +RUN autoreconf -if +RUN ./configure --prefix=/usr +RUN make +RUN make install +RUN ldconfig + +# Valgrind for kresd CI +RUN apt-get install valgrind wget -y -qqq +RUN wget https://raw.githubusercontent.com/LuaJIT/LuaJIT/v2.0.4/src/lj.supp -O /lj.supp +# TODO: rebuild LuaJIT with Valgrind support + +# Lua lint for kresd CI +RUN apt-get install luarocks -y -qqq +RUN luarocks install luacheck + +# code coverage +RUN apt-get install -y -qqq lcov +RUN luarocks install luacov + +# LuaJIT binary for stand-alone scripting +RUN apt-get install -y -qqq luajit + +# clang for kresd CI, version updated as debian updates it +RUN apt-get install -y -qqq clang clang-tools clang-tidy diff --git a/ci/debian-unstable/README b/ci/debian-unstable/README new file mode 100644 index 0000000..4dbcd40 --- /dev/null +++ b/ci/debian-unstable/README @@ -0,0 +1 @@ +Docker image based on debian-unstable specifically for newer Clang. diff --git a/ci/fedora/Dockerfile b/ci/fedora/Dockerfile new file mode 100644 index 0000000..0997a1e --- /dev/null +++ b/ci/fedora/Dockerfile @@ -0,0 +1,22 @@ +FROM fedora:29 + +WORKDIR "/tmp" +CMD ["/bin/bash"] + +RUN dnf install -y mock rpkg git + +# for scripts/make-distrofiles.sh +RUN dnf install -y dpkg-dev perl-Digest-* + +# add OBS repo with Knot DNS to mock +RUN curl -Lo obs-epel7.repo 'https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-testing/CentOS_7_EPEL/home:CZ-NIC:knot-resolver-testing.repo' +RUN sed -i -e "/^config_opts\[.yum.conf.]/r obs-epel7.repo" /etc/mock/epel-7-x86_64.cfg +RUN curl -Lo obs-fedora.repo 'https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-testing/Fedora_29/home:CZ-NIC:knot-resolver-testing.repo' +RUN sed -i -e "/^config_opts\[.yum.conf.]/r obs-fedora.repo" /etc/mock/fedora-29-x86_64.cfg + +# cache packages in mock to speed up CI tests +# This would require privileged build: https://github.com/moby/moby/issues/1916 +# RUN dnf download --source knot-resolver +# RUN mock --no-clean --dnf --old-chroot -r epel-7-x86_64 --rebuild knot-resolver-*.src.rpm +# RUN mock --no-clean --old-chroot -r fedora-29-x86_64 --rebuild knot-resolver-*.src.rpm +# RUN rm *.src.rpm diff --git a/ci/pytests/lint.sh b/ci/pytests/lint.sh new file mode 100755 index 0000000..b6c0bfc --- /dev/null +++ b/ci/pytests/lint.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +python3 -m flake8 --max-line-length=100 tests/pytests +FLAKE8=$? + +ci/pytests/pylint-run.sh +PYLINT=$? + +if [ $PYLINT -ne 0 ]; then + exit 1 +fi +if [ $FLAKE8 -ne 0 ]; then + exit 1 +fi + +exit 0 diff --git a/ci/pytests/pylint-run.sh b/ci/pytests/pylint-run.sh new file mode 100755 index 0000000..159a830 --- /dev/null +++ b/ci/pytests/pylint-run.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +# Find Python modules and standalone Python scripts +FILES=$(find ./tests/pytests \ + -type d -exec test -e '{}/__init__.py' \; -print -prune -o \ + -name '*.py' -print) + +python3 -m pylint -j 0 --rcfile ./tests/pytests/pylintrc ${FILES} diff --git a/ci/pytests/run-extended.sh b/ci/pytests/run-extended.sh new file mode 100755 index 0000000..694fba5 --- /dev/null +++ b/ci/pytests/run-extended.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Execute extended, long-running test suite + +python3 -m pytest -ra --capture=no tests/pytests/conn_flood.py diff --git a/ci/pytests/run.sh b/ci/pytests/run.sh new file mode 100755 index 0000000..e1b55af --- /dev/null +++ b/ci/pytests/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python3 -m pytest --html pytests.html --self-contained-html -dn 24 tests/pytests diff --git a/ci/respdiff/kresd.config b/ci/respdiff/kresd.config new file mode 100644 index 0000000..c733601 --- /dev/null +++ b/ci/respdiff/kresd.config @@ -0,0 +1,24 @@ +-- Refer to manual: https://knot-resolver.readthedocs.io/en/latest/daemon.html#configuration +-- Listen on localhost and external interface +net.listen('127.0.0.1', 5353) +net.listen('127.0.0.1', 8853, { tls = true }) +net.ipv6=false + +-- Auto-maintain root TA +trust_anchors.file = '.local/etc/knot-resolver/root.keys' + +-- Large cache size, so we don't need to flush often +-- This can be larger than available RAM, least frequently accessed +-- records will be paged out +cache.size = 1024 * MB + +-- Load Useful modules +modules = { + 'workarounds < iterate', + 'policy', -- Block queries to local zones/bad sites + 'view', -- Views for certain clients + 'hints', -- Load /etc/hosts and allow custom root hints + 'stats', -- Track internal statistics +} + +verbose(true) diff --git a/ci/respdiff/respdiff-tcp.conf b/ci/respdiff/respdiff-tcp.conf new file mode 100644 index 0000000..3d42c31 --- /dev/null +++ b/ci/respdiff/respdiff-tcp.conf @@ -0,0 +1,50 @@ +[sendrecv] +# in seconds +timeout = 11 +# number of queries to run simultaneously +jobs = 64 +# in seconds (float); delay each query by a random time (uniformly distributed) between min and max; set max to 0 to disable +time_delay_min = 0 +time_delay_max = 0 + +[servers] +names = kresd, bind, unbound +# symbolic names of DNS servers under test +# separate multiple values by , + +# each symbolic name in [servers] section refers to config section +# containing IP address and port of particular server +[kresd] +ip = 127.0.0.1 +port = 5353 +transport = tcp +graph_color = #00a2e2 +restart_script = ./ci/respdiff/restart-kresd.sh + +[bind] +ip = 127.0.0.1 +port = 53533 +transport = udp +graph_color = #e2a000 +restart_script = ./ci/respdiff/restart-bind.sh + +[unbound] +ip = 127.0.0.1 +port = 53535 +transport = udp +graph_color = #218669 +restart_script = ./ci/respdiff/restart-unbound.sh + +[diff] +# symbolic name of server under test +# other servers are used as reference when comparing answers from the target +target = kresd + +# fields and comparison methods used when comparing two DNS messages +criteria = opcode, rcode, flags, question, answertypes, answerrrsigs +# other supported criteria values: authority, additional, edns, nsid + +[report] +# diffsum reports mismatches in field values in this order +# if particular message has multiple mismatches, it is counted only once into category with highest weight +field_weights = timeout, malformed, opcode, question, rcode, flags, answertypes, answerrrsigs, answer, authority, additional, edns, nsid diff --git a/ci/respdiff/respdiff-tls.conf b/ci/respdiff/respdiff-tls.conf new file mode 100644 index 0000000..9f44cea --- /dev/null +++ b/ci/respdiff/respdiff-tls.conf @@ -0,0 +1,50 @@ +[sendrecv] +# in seconds +timeout = 11 +# number of queries to run simultaneously +jobs = 64 +# in seconds (float); delay each query by a random time (uniformly distributed) between min and max; set max to 0 to disable +time_delay_min = 0 +time_delay_max = 0 + +[servers] +names = kresd, bind, unbound +# symbolic names of DNS servers under test +# separate multiple values by , + +# each symbolic name in [servers] section refers to config section +# containing IP address and port of particular server +[kresd] +ip = 127.0.0.1 +port = 8853 +transport = tls +graph_color = #00a2e2 +restart_script = ./ci/respdiff/restart-kresd.sh + +[bind] +ip = 127.0.0.1 +port = 53533 +transport = udp +graph_color = #e2a000 +restart_script = ./ci/respdiff/restart-bind.sh + +[unbound] +ip = 127.0.0.1 +port = 53535 +transport = udp +graph_color = #218669 +restart_script = ./ci/respdiff/restart-unbound.sh + +[diff] +# symbolic name of server under test +# other servers are used as reference when comparing answers from the target +target = kresd + +# fields and comparison methods used when comparing two DNS messages +criteria = opcode, rcode, flags, question, answertypes, answerrrsigs +# other supported criteria values: authority, additional, edns, nsid + +[report] +# diffsum reports mismatches in field values in this order +# if particular message has multiple mismatches, it is counted only once into category with highest weight +field_weights = timeout, malformed, opcode, question, rcode, flags, answertypes, answerrrsigs, answer, authority, additional, edns, nsid diff --git a/ci/respdiff/respdiff-udp.conf b/ci/respdiff/respdiff-udp.conf new file mode 100644 index 0000000..4db7da0 --- /dev/null +++ b/ci/respdiff/respdiff-udp.conf @@ -0,0 +1,50 @@ +[sendrecv] +# in seconds +timeout = 11 +# number of queries to run simultaneously +jobs = 64 +# in seconds (float); delay each query by a random time (uniformly distributed) between min and max; set max to 0 to disable +time_delay_min = 0 +time_delay_max = 0 + +[servers] +names = kresd, bind, unbound +# symbolic names of DNS servers under test +# separate multiple values by , + +# each symbolic name in [servers] section refers to config section +# containing IP address and port of particular server +[kresd] +ip = 127.0.0.1 +port = 5353 +transport = udp +graph_color = #00a2e2 +restart_script = ./ci/respdiff/restart-kresd.sh + +[bind] +ip = 127.0.0.1 +port = 53533 +transport = udp +graph_color = #e2a000 +restart_script = ./ci/respdiff/restart-bind.sh + +[unbound] +ip = 127.0.0.1 +port = 53535 +transport = udp +graph_color = #218669 +restart_script = ./ci/respdiff/restart-unbound.sh + +[diff] +# symbolic name of server under test +# other servers are used as reference when comparing answers from the target +target = kresd + +# fields and comparison methods used when comparing two DNS messages +criteria = opcode, rcode, flags, question, answertypes, answerrrsigs +# other supported criteria values: authority, additional, edns, nsid + +[report] +# diffsum reports mismatches in field values in this order +# if particular message has multiple mismatches, it is counted only once into category with highest weight +field_weights = timeout, malformed, opcode, question, rcode, flags, answertypes, answerrrsigs, answer, authority, additional, edns, nsid diff --git a/ci/respdiff/restart-bind.sh b/ci/respdiff/restart-bind.sh new file mode 100755 index 0000000..89fd832 --- /dev/null +++ b/ci/respdiff/restart-bind.sh @@ -0,0 +1,2 @@ +#!/bin/sh +service bind9 restart diff --git a/ci/respdiff/restart-kresd.sh b/ci/respdiff/restart-kresd.sh new file mode 100755 index 0000000..dfe82cf --- /dev/null +++ b/ci/respdiff/restart-kresd.sh @@ -0,0 +1,11 @@ +#!/bin/sh +exec > /dev/null +exec 2>&1 + +PREFIX=$(pwd)/.local +killall -w kresd +rm -f '*.mdb' +LD_LIBRARY_PATH=$PREFIX/lib $PREFIX/sbin/kresd -f 1 -q -c $(pwd)/ci/respdiff/kresd.config &>>kresd.log & + +# wait until socket is receiving connections +sleep 1 diff --git a/ci/respdiff/restart-unbound.sh b/ci/respdiff/restart-unbound.sh new file mode 100755 index 0000000..c9525c0 --- /dev/null +++ b/ci/respdiff/restart-unbound.sh @@ -0,0 +1,2 @@ +#!/bin/sh +service unbound restart diff --git a/ci/respdiff/run-respdiff-tests.sh b/ci/respdiff/run-respdiff-tests.sh new file mode 100755 index 0000000..c55243f --- /dev/null +++ b/ci/respdiff/run-respdiff-tests.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# $1 == udp/tcp/tls, it selects configuration file to use +# respdiff scripts must be present in /var/opt/respdiff +set -o errexit -o nounset -o xtrace + +NDIFFREPRO=3 + +wget -qO- https://gitlab.labs.nic.cz/knot/respdiff/snippets/238/raw?inline=false | head -n 5000 > /tmp/queries.txt +mkdir results +rm -rf respdiff.db + +CONFIG="$(pwd)/ci/respdiff/respdiff-${1}.conf" +/var/opt/respdiff/qprep.py respdiff.db < /tmp/queries.txt +time /var/opt/respdiff/orchestrator.py respdiff.db -c "${CONFIG}" +time /var/opt/respdiff/msgdiff.py respdiff.db -c "${CONFIG}" +for i in $(seq $NDIFFREPRO); do + time /var/opt/respdiff/diffrepro.py -c "${CONFIG}" respdiff.db +done +/var/opt/respdiff/diffsum.py respdiff.db -c "${CONFIG}" > results/respdiff.txt +/var/opt/respdiff/histogram.py respdiff.db -c "${CONFIG}" -o results/histogram.svg +: minimize LMDB and log size so they can be effectively archived +mkdir results/respdiff.db +mdb_copy -c respdiff.db results/respdiff.db +xz -9 results/respdiff.db/data.mdb +xz kresd.log diff --git a/ci/respdiff/start-resolvers.sh b/ci/respdiff/start-resolvers.sh new file mode 100755 index 0000000..1a63de4 --- /dev/null +++ b/ci/respdiff/start-resolvers.sh @@ -0,0 +1,11 @@ +#run unbound +service unbound start && service unbound status; +# dig @localhost -p 53535 + +#run bind +service bind9 start && service bind9 status; +# dig @localhost -p 53533 + +#run kresd +LD_LIBRARY_PATH=$PREFIX/lib $PREFIX/sbin/kresd -f 1 -q -c $(pwd)/ci/respdiff/kresd.config &>kresd.log & +# dig @localhost -p 5353 diff --git a/ci/travis.py b/ci/travis.py new file mode 100755 index 0000000..f0909ae --- /dev/null +++ b/ci/travis.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +import json +import time +import sys + +import requests + + +BRANCH_API_ENDPOINT = "https://api.travis-ci.com/repos/CZ-NIC/knot-resolver/branches/{branch}" +JOB_URL = "https://travis-ci.com/CZ-NIC/knot-resolver/jobs/{job_id}" +TIMEOUT = 600 # 10 mins max +POLL_DELAY = 15 + +job_id = None + + +def exit(msg='', code=1): + print(msg, file=sys.stderr) + if job_id is not None: + print(JOB_URL.format(job_id=job_id)) + sys.exit(code) + + +end_time = time.time() + TIMEOUT +while time.time() < end_time: + response = requests.get( + BRANCH_API_ENDPOINT.format(branch=sys.argv[1]), + headers={"Accept": "application/vnd.travis-ci.2.1+json"}) + if response.status_code == 404: + pass # not created yet? + elif response.status_code == 200: + data = json.loads(response.content.decode('utf-8')) + state = data['branch']['state'] + try: + job_id = data['branch']['job_ids'][0] + except KeyError: + pass + + if state == "errored": + exit("Travis CI Result: ERRORED!") + elif state == "passed": + exit("Travis CI Result: PASSED!", code=0) + else: + exit("API Response Code: {code}".format(response.status_code), code=2) + time.sleep(POLL_DELAY) + +exit("Timed out!") |