From f39e1fe340ef84496ad342dd8fbe91e35f646677 Mon Sep 17 00:00:00 2001 From: Charles-Henri Bruyand Date: Wed, 2 May 2018 14:07:13 +0200 Subject: [PATCH] rec api: add subtree option to the cache flush endpoint (cherry picked from commit d19c22a15f8e75ebc6ff22665ba8e8c2152957db) --- .../docs/http-api/endpoint-cache.rst | 1 + pdns/ws-recursor.cc | 7 +++--- regression-tests.api/runtests | 2 ++ regression-tests.api/runtests.py | 17 +++++++++++++ regression-tests.api/test_Cache.py | 25 ++++++++++++++++++- regression-tests.api/test_helper.py | 9 ++++++- 6 files changed, 56 insertions(+), 5 deletions(-) diff --git a/pdns/recursordist/docs/http-api/endpoint-cache.rst b/pdns/recursordist/docs/http-api/endpoint-cache.rst index 7a7848deb..4b642f32d 100644 --- a/pdns/recursordist/docs/http-api/endpoint-cache.rst +++ b/pdns/recursordist/docs/http-api/endpoint-cache.rst @@ -7,6 +7,7 @@ Cache manipulation endpoint :query server_id: The name of the server :query domain: The domainname to flush for + :query subtree: If set to `true`, also flush the whole subtree (default=`false`) **Example Response:** diff --git a/pdns/ws-recursor.cc b/pdns/ws-recursor.cc index 7b2c9bc85..748441c9a 100644 --- a/pdns/ws-recursor.cc +++ b/pdns/ws-recursor.cc @@ -373,10 +373,11 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { throw HttpMethodNotAllowedException(); DNSName canon = apiNameToDNSName(req->getvars["domain"]); + bool subtree = (req->getvars["subtree"].compare("true") == 0); - int count = broadcastAccFunction(boost::bind(pleaseWipeCache, canon, false)); - count += broadcastAccFunction(boost::bind(pleaseWipePacketCache, canon, false)); - count += broadcastAccFunction(boost::bind(pleaseWipeAndCountNegCache, canon, false)); + int count = broadcastAccFunction(boost::bind(pleaseWipeCache, canon, subtree)); + count += broadcastAccFunction(boost::bind(pleaseWipePacketCache, canon, subtree)); + count += broadcastAccFunction(boost::bind(pleaseWipeAndCountNegCache, canon, subtree)); resp->setBody(Json::object { { "count", count }, { "result", "Flushed cache." } diff --git a/regression-tests.api/runtests b/regression-tests.api/runtests index beee380d3..47f25f0f1 100755 --- a/regression-tests.api/runtests +++ b/regression-tests.api/runtests @@ -9,6 +9,8 @@ fi python -V pip install -r requirements.txt +export SDIG=$(type -P sdig) + set -e if [ "${PDNS_DEBUG}" = "YES" ]; then set -x diff --git a/regression-tests.api/runtests.py b/regression-tests.api/runtests.py index 8435b5eee..b9586259e 100755 --- a/regression-tests.api/runtests.py +++ b/regression-tests.api/runtests.py @@ -12,6 +12,7 @@ import time SQLITE_DB = 'pdns.sqlite3' WEBPORT = '5556' +DNSPORT = 5555 APIKEY = '1234567890abcdefghijklmnopq-key' PDNSUTIL_CMD = ["../pdns/pdnsutil", "--config-dir=."] @@ -63,6 +64,12 @@ def ensure_empty_dir(name): shutil.rmtree(name) os.mkdir(name) +def format_call_args(cmd): + return "$ '%s'" % ("' '".join(cmd)) + +def run_check_call(cmd, *args, **kwargs): + print format_call_args(cmd) + subprocess.check_call(cmd, *args, **kwargs) wait = ('--wait' in sys.argv) if wait: @@ -83,6 +90,14 @@ daemon = sys.argv[1] pdns_recursor = os.environ.get("PDNSRECURSOR", "../pdns/recursordist/pdns_recursor") +# Take sdig if it exists (recursor in travis), otherwise build it from Authoritative source. +sdig = os.environ.get("SDIG", "") +if sdig: + sdig = os.path.abspath(sdig) +if not sdig or not os.path.exists(sdig): + run_check_call(["make", "-C", "../pdns", "sdig"]) + sdig = "../pdns/sdig" + if daemon == 'authoritative': # Prepare sqlite DB with some zones. @@ -155,6 +170,8 @@ test_env.update({ 'DAEMON': daemon, 'SQLITE_DB': SQLITE_DB, 'PDNSUTIL_CMD': ' '.join(PDNSUTIL_CMD), + 'SDIG': sdig, + 'DNSPORT': str(DNSPORT) }) try: diff --git a/regression-tests.api/test_Cache.py b/regression-tests.api/test_Cache.py index 6a3f618d5..3ab4ea68c 100644 --- a/regression-tests.api/test_Cache.py +++ b/regression-tests.api/test_Cache.py @@ -1,4 +1,4 @@ -from test_helper import ApiTestCase, is_auth, is_recursor +from test_helper import ApiTestCase, is_auth, is_recursor, sdig class Servers(ApiTestCase): @@ -9,6 +9,29 @@ class Servers(ApiTestCase): data = r.json() self.assertIn('count', data) + def test_flush_count(self): + sdig("ns1.example.com", 'A') + r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=ns1.example.com.")) + self.assert_success_json(r) + data = r.json() + self.assertIn('count', data) + self.assertEquals(1, data['count']) + + def test_flush_subtree(self): + sdig("ns1.example.com", 'A') + sdig("ns2.example.com", 'A') + r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=example.com.&subtree=false")) + self.assert_success_json(r) + data = r.json() + self.assertIn('count', data) + # Yes, this is 0 in 4.1.x, 1 in master, because in master we send a query for example.com "to create statistic data" + self.assertEquals(0, data['count']) + r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=example.com.&subtree=true")) + self.assert_success_json(r) + data = r.json() + self.assertIn('count', data) + self.assertEquals(2, data['count']) + def test_flush_root(self): r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=.")) self.assert_success_json(r) diff --git a/regression-tests.api/test_helper.py b/regression-tests.api/test_helper.py index 14c2ea4c2..46a80f831 100644 --- a/regression-tests.api/test_helper.py +++ b/regression-tests.api/test_helper.py @@ -9,7 +9,8 @@ import subprocess DAEMON = os.environ.get('DAEMON', 'authoritative') PDNSUTIL_CMD = os.environ.get('PDNSUTIL_CMD', 'NOT_SET BUT_THIS MIGHT_BE_A_LIST').split(' ') SQLITE_DB = os.environ.get('SQLITE_DB', 'pdns.sqlite3') - +SDIG = os.environ.get('SDIG', 'sdig') +DNSPORT = os.environ.get('DNSPORT', '53') class ApiTestCase(unittest.TestCase): @@ -77,3 +78,9 @@ def get_db_records(zonename, qtype): def pdnsutil_rectify(zonename): """Run pdnsutil rectify-zone on the given zone.""" subprocess.check_call(PDNSUTIL_CMD + ['rectify-zone', zonename]) + +def sdig(*args): + try: + return subprocess.check_call([SDIG, '127.0.0.1', str(DNSPORT)] + list(args)) + except subprocess.CalledProcessError as except_inst: + raise RuntimeError("sdig %s %s failed: %s" % (command, args, except_inst.output.decode('ascii', errors='replace'))) -- 2.40.0