From 6aa6d7be58e66a9e2669af6bbf7e9c709dc36548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Fri, 27 Mar 2020 10:13:31 +0100 Subject: [PATCH] Add tests for zone timers using the pytest testing framework --- bin/tests/system/statschannel/.gitignore | 2 + bin/tests/system/statschannel/clean.sh | 10 ++- bin/tests/system/statschannel/conftest.py | 74 ++++++++++++++++ bin/tests/system/statschannel/helper.py | 58 +++++++++++++ bin/tests/system/statschannel/ns1/example.db | 47 +++++++++++ .../system/statschannel/ns1/named.conf.in | 41 +++++++++ .../system/statschannel/ns2/named.conf.in | 2 +- .../system/statschannel/ns3/named.conf.in | 41 +++++++++ bin/tests/system/statschannel/setup.sh | 9 +- bin/tests/system/statschannel/tests-json.py | 75 +++++++++++++++++ bin/tests/system/statschannel/tests-xml.py | 84 +++++++++++++++++++ bin/tests/system/statschannel/tests.sh | 0 util/copyrights | 4 + 13 files changed, 437 insertions(+), 10 deletions(-) create mode 100644 bin/tests/system/statschannel/.gitignore create mode 100644 bin/tests/system/statschannel/conftest.py create mode 100644 bin/tests/system/statschannel/helper.py create mode 100644 bin/tests/system/statschannel/ns1/example.db create mode 100644 bin/tests/system/statschannel/ns1/named.conf.in create mode 100644 bin/tests/system/statschannel/ns3/named.conf.in create mode 100755 bin/tests/system/statschannel/tests-json.py create mode 100755 bin/tests/system/statschannel/tests-xml.py mode change 100644 => 100755 bin/tests/system/statschannel/tests.sh diff --git a/bin/tests/system/statschannel/.gitignore b/bin/tests/system/statschannel/.gitignore new file mode 100644 index 0000000000..44fb46c3c0 --- /dev/null +++ b/bin/tests/system/statschannel/.gitignore @@ -0,0 +1,2 @@ +/.cache/ +/__pycache__/ diff --git a/bin/tests/system/statschannel/clean.sh b/bin/tests/system/statschannel/clean.sh index c9edbde9ee..243a90612c 100644 --- a/bin/tests/system/statschannel/clean.sh +++ b/bin/tests/system/statschannel/clean.sh @@ -12,9 +12,9 @@ rm -f traffic traffic.out.* traffic.json.* traffic.xml.* rm -f zones zones.out.* zones.json.* zones.xml.* zones.expect.* rm -f dig.out* -rm -f */named.memstats -rm -f */named.conf -rm -f */named.run* +rm -f ns*/named.memstats +rm -f ns*/named.conf +rm -f ns*/named.run* rm -f ns*/named.lock rm -f ns*/named.stats rm -f xml.*stats json.*stats @@ -24,4 +24,6 @@ rm -f ns*/managed-keys.bind* rm -f ns2/Kdnssec* ns2/dnssec.*.id rm -f ns2/Kmanykeys* ns2/manykeys.*.id rm -f ns2/*.db.signed* ns2/dsset-*. ns2/*.jbk -rm -f ns2/core +rm -f ns2/dnssec.db.signed* ns2/dsset-dnssec. +rm -f ns3/*.db +rm -rf /.cache /__pycache__ diff --git a/bin/tests/system/statschannel/conftest.py b/bin/tests/system/statschannel/conftest.py new file mode 100644 index 0000000000..d62b6af29b --- /dev/null +++ b/bin/tests/system/statschannel/conftest.py @@ -0,0 +1,74 @@ +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. +############################################################################ + +import pytest +import os + +def pytest_configure(config): + config.addinivalue_line( + "markers", "requests: mark tests that need requests to function" + ) + config.addinivalue_line( + "markers", "json: mark tests that need json to function" + ) + config.addinivalue_line( + "markers", "xml: mark tests that need xml.etree to function" + ) + + +def pytest_collection_modifyitems(config, items): + # Test for requests module + skip_requests = pytest.mark.skip(reason="need requests module to run") + try: + import requests # noqa: F401 + except ModuleNotFoundError: + for item in items: + if "requests" in item.keywords: + item.add_marker(skip_requests) + # Test for json module + skip_json = pytest.mark.skip(reason="need json module to run") + try: + import json # noqa: F401 + except ModuleNotFoundError: + for item in items: + if "json" in item.keywords: + item.add_marker(skip_json) + # Test for xml module + skip_xml = pytest.mark.skip(reason="need xml module to run") + try: + import xml # noqa: F401 + except ModuleNotFoundError: + for item in items: + if "xml" in item.keywords: + item.add_marker(skip_xml) + # Test if JSON statistics channel was enabled + no_jsonstats = pytest.mark.skip(reason="need JSON statistics to be enabled") + if os.getenv("HAVEJSONSTATS") is None: + for item in items: + if "json" in item.keywords: + item.add_marker(no_jsonstats) + # Test if XML statistics channel was enabled + no_xmlstats = pytest.mark.skip(reason="need XML statistics to be enabled") + if os.getenv("HAVEXMLSTATS") is None: + for item in items: + if "xml" in item.keywords: + item.add_marker(no_xmlstats) + + +@pytest.fixture +def statsport(request): + port = os.getenv("EXTRAPORT1") + if port is None: + port = 5301 + else: + port = int(port) + + return port diff --git a/bin/tests/system/statschannel/helper.py b/bin/tests/system/statschannel/helper.py new file mode 100644 index 0000000000..0865b3031b --- /dev/null +++ b/bin/tests/system/statschannel/helper.py @@ -0,0 +1,58 @@ +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. +############################################################################ + +from datetime import datetime, timedelta + +# ISO datetime format without msec +fmt = '%Y-%m-%dT%H:%M:%SZ' + +# The constants were taken from BIND 9 source code (lib/dns/zone.c) +max_refresh = timedelta(seconds=2419200) # 4 weeks +max_expires = timedelta(seconds=14515200) # 24 weeks +now = datetime.utcnow().replace(microsecond=0) +dayzero = datetime.utcfromtimestamp(0).replace(microsecond=0) + + +# Generic helper functions +def check_expires(expires, min, max): + assert expires >= min + assert expires <= max + + +def check_refresh(refresh, min, max): + assert refresh >= min + assert refresh <= max + + +def check_loaded(loaded, expected): + # Sanity check the zone timers values + assert loaded == expected + assert loaded < now + + +def check_zone_timers(loaded, expires, refresh, loaded_exp): + # Sanity checks the zone timers values + if expires is not None: + check_expires(expires, now, now + max_expires) + if refresh is not None: + check_refresh(refresh, now, now + max_refresh) + check_loaded(loaded, loaded_exp) + + +def zone_mtime(zonedir, name): + import os + import os.path + from datetime import datetime + + si = os.stat(os.path.join(zonedir, "{}.db".format(name))) + mtime = datetime.utcfromtimestamp(si.st_mtime).replace(microsecond=0) + + return mtime diff --git a/bin/tests/system/statschannel/ns1/example.db b/bin/tests/system/statschannel/ns1/example.db new file mode 100644 index 0000000000..b65651aa2d --- /dev/null +++ b/bin/tests/system/statschannel/ns1/example.db @@ -0,0 +1,47 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 ; 5 minutes +example IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example. NS ns2.example. +ns2.example. A 10.53.0.2 + +$ORIGIN example. +a A 10.0.0.1 + MX 10 mail.example. +short TXT "short text" +long TXT ( + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglong" + ) + +mail A 10.0.0.2 diff --git a/bin/tests/system/statschannel/ns1/named.conf.in b/bin/tests/system/statschannel/ns1/named.conf.in new file mode 100644 index 0000000000..fa1cd57a6c --- /dev/null +++ b/bin/tests/system/statschannel/ns1/named.conf.in @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify explicit; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.1 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type master; + file "example.db"; + allow-transfer { any; }; +}; diff --git a/bin/tests/system/statschannel/ns2/named.conf.in b/bin/tests/system/statschannel/ns2/named.conf.in index 70aadf8b28..9eebe560d3 100644 --- a/bin/tests/system/statschannel/ns2/named.conf.in +++ b/bin/tests/system/statschannel/ns2/named.conf.in @@ -18,7 +18,7 @@ options { listen-on { 10.53.0.2; }; listen-on-v6 { none; }; recursion no; - notify yes; + notify no; minimal-responses no; version none; // make statistics independent of the version number }; diff --git a/bin/tests/system/statschannel/ns3/named.conf.in b/bin/tests/system/statschannel/ns3/named.conf.in new file mode 100644 index 0000000000..e78cff9a7a --- /dev/null +++ b/bin/tests/system/statschannel/ns3/named.conf.in @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion no; + notify no; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.3 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type secondary; + file "example.db"; + masters { 10.53.0.1; }; +}; diff --git a/bin/tests/system/statschannel/setup.sh b/bin/tests/system/statschannel/setup.sh index 3a8577eb72..2ce6fde81d 100644 --- a/bin/tests/system/statschannel/setup.sh +++ b/bin/tests/system/statschannel/setup.sh @@ -12,9 +12,8 @@ # shellcheck source=conf.sh . "$SYSTEMTESTTOP/conf.sh" -copy_setports ns2/named.conf.in ns2/named.conf +for conf in ns*/named.conf.in; do + copy_setports "$conf" "$(dirname "$conf")/$(basename "$conf" .in)" +done -( - cd ns2 - $SHELL sign.sh -) +(cd ns2 && $SHELL sign.sh) diff --git a/bin/tests/system/statschannel/tests-json.py b/bin/tests/system/statschannel/tests-json.py new file mode 100755 index 0000000000..f9bd5ec78f --- /dev/null +++ b/bin/tests/system/statschannel/tests-json.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. +############################################################################ + +import pytest +from datetime import datetime +from helper import fmt, zone_mtime, check_zone_timers, dayzero + + +# JSON helper functions +def fetch_json(statsip, statsport): + import requests + + r = requests.get("http://{}:{}/json/v1/zones".format(statsip, statsport)) + assert r.status_code == 200 + + data = r.json() + + return data["views"]["_default"]["zones"] + + +def load_timers_from_json(zone, primary=True): + name = zone['name'] + + # Check if the primary zone timer exists + assert 'loaded' in zone + loaded = datetime.strptime(zone['loaded'], fmt) + + if primary: + # Check if the secondary zone timers does not exist + assert 'expires' not in zone + assert 'refresh' not in zone + expires = None + refresh = None + else: + assert 'expires' in zone + assert 'refresh' in zone + expires = datetime.strptime(zone['expires'], fmt) + refresh = datetime.strptime(zone['refresh'], fmt) + + return (name, loaded, expires, refresh) + + +@pytest.mark.json +@pytest.mark.requests +def test_zone_timers_primary_json(statsport): + statsip = "10.53.0.1" + zonedir = "ns1" + + zones = fetch_json(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers_from_json(zone, True) + mtime = zone_mtime(zonedir, name) + check_zone_timers(loaded, expires, refresh, mtime) + + +@pytest.mark.json +@pytest.mark.requests +def test_zone_timers_secondary_json(statsport): + statsip = "10.53.0.3" + + zones = fetch_json(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers_from_json(zone, False) + check_zone_timers(loaded, expires, refresh, dayzero) diff --git a/bin/tests/system/statschannel/tests-xml.py b/bin/tests/system/statschannel/tests-xml.py new file mode 100755 index 0000000000..dcd2d76598 --- /dev/null +++ b/bin/tests/system/statschannel/tests-xml.py @@ -0,0 +1,84 @@ +#!/usr/bin/python3 +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. +############################################################################ + +import pytest +from datetime import datetime +from helper import fmt, zone_mtime, check_zone_timers, dayzero + + +# XML helper functions +def fetch_xml(statsip, statsport): + import xml.etree.ElementTree as ET + import requests + + r = requests.get("http://{}:{}/xml/v3/zones".format(statsip, statsport)) + assert r.status_code == 200 + + root = ET.fromstring(r.text) + + default_view = None + for view in root.find('views').iter('view'): + if view.attrib['name'] == "_default": + default_view = view + break + assert default_view is not None + + return default_view.find('zones').findall('zone') + + +def load_timers_from_xml(zone, primary=True): + name = zone.attrib['name'] + + loaded_el = zone.find('loaded') + assert loaded_el is not None + loaded = datetime.strptime(loaded_el.text, fmt) + + expires_el = zone.find('expires') + refresh_el = zone.find('refresh') + if primary: + assert expires_el is None + assert refresh_el is None + expires = None + refresh = None + else: + assert expires_el is not None + assert refresh_el is not None + expires = datetime.strptime(expires_el.text, fmt) + refresh = datetime.strptime(refresh_el.text, fmt) + + return (name, loaded, expires, refresh) + + +@pytest.mark.xml +@pytest.mark.requests +def test_zone_timers_primary_xml(statsport): + statsip = "10.53.0.1" + zonedir = "ns1" + + zones = fetch_xml(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers_from_xml(zone, True) + mtime = zone_mtime(zonedir, name) + check_zone_timers(loaded, expires, refresh, mtime) + + +@pytest.mark.xml +@pytest.mark.requests +def test_zone_timers_secondary_xml(statsport): + statsip = "10.53.0.3" + + zones = fetch_xml(statsip, statsport) + + for zone in zones: + (name, loaded, expires, refresh) = load_timers_from_xml(zone, False) + check_zone_timers(loaded, expires, refresh, dayzero) diff --git a/bin/tests/system/statschannel/tests.sh b/bin/tests/system/statschannel/tests.sh old mode 100644 new mode 100755 diff --git a/util/copyrights b/util/copyrights index 91a31b2ea5..40db450438 100644 --- a/util/copyrights +++ b/util/copyrights @@ -808,12 +808,16 @@ ./bin/tests/system/statistics/setup.sh SH 2018,2019,2020 ./bin/tests/system/statistics/tests.sh SH 2012,2015,2016,2017,2018,2019,2020 ./bin/tests/system/statschannel/clean.sh SH 2015,2016,2017,2018,2019,2020 +./bin/tests/system/statschannel/conftest.py PYTHON 2020 ./bin/tests/system/statschannel/fetch.pl PERL 2015,2016,2018,2019,2020 +./bin/tests/system/statschannel/helper.py PYTHON 2020 ./bin/tests/system/statschannel/mem-xml.pl PERL 2017,2018,2019,2020 ./bin/tests/system/statschannel/ns2/sign.sh SH 2019,2020 ./bin/tests/system/statschannel/server-json.pl PERL 2015,2016,2017,2018,2019,2020 ./bin/tests/system/statschannel/server-xml.pl PERL 2015,2016,2017,2018,2019,2020 ./bin/tests/system/statschannel/setup.sh SH 2018,2019,2020 +./bin/tests/system/statschannel/tests-json.py PYTHON-BIN 2020 +./bin/tests/system/statschannel/tests-xml.py PYTHON-BIN 2020 ./bin/tests/system/statschannel/tests.sh SH 2015,2016,2017,2018,2019,2020 ./bin/tests/system/statschannel/traffic-json.pl PERL 2015,2016,2017,2018,2019,2020 ./bin/tests/system/statschannel/traffic-xml.pl PERL 2015,2016,2017,2018,2019,2020