* GET: Query. Success reply: `200 OK`
* PUT: Action/Execute. Success reply: `200 OK`
+Action/Execute methods return a JSON body of this format:
+
+ {
+ "message": "result message"
+ }
+
Authentication
--------------
Clients MUST NOT send a body.
-**TODO**: Not yet implemented.
-
URL: /servers/:server\_id/zones/:zone\_id/axfr-retrieve
-------------------------------------------------------
Clients MUST NOT send a body.
-**TODO**: Not yet implemented.
URL: /servers/:server\_id/zones/:zone\_id/check
-----------------------------------------------
+/*
+ Copyright (C) 2002 - 2015 PowerDNS.COM BV
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation
+
+ Additionally, the license of this program contains a special
+ exception which allows to distribute the program in binary form when
+ it is linked against OpenSSL.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
#include "json.hh"
#include "namespaces.hh"
#include "misc.hh"
doc.AddMember("error", jerror, doc.GetAllocator());
return makeStringFromDocument(doc);
}
+
+/* success response */
+string returnJsonMessage(const string& message)
+{
+ Document doc;
+ doc.SetObject();
+ Value jmessage;
+ jmessage.SetString(message.c_str());
+ doc.AddMember("result", jmessage, doc.GetAllocator());
+ return makeStringFromDocument(doc);
+}
std::string returnJsonObject(const std::map<std::string, std::string>& items);
std::string returnJsonError(const std::string& error);
+std::string returnJsonMessage(const std::string& message);
std::string makeStringFromDocument(const rapidjson::Document& doc);
int intFromJson(const rapidjson::Value& container, const char* key);
int intFromJson(const rapidjson::Value& container, const char* key, const int default_value);
/*
- Copyright (C) 2002 - 2014 PowerDNS.COM BV
+ Copyright (C) 2002 - 2015 PowerDNS.COM BV
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
#include "dnsseckeeper.hh"
#include <iomanip>
#include "zoneparser-tng.hh"
+#include "common_startup.hh"
#ifdef HAVE_CONFIG_H
# include <config.h>
}
}
+static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
+ string zonename = apiZoneIdToName(req->parameters["id"]);
+
+ if(req->method != "PUT")
+ throw HttpMethodNotAllowedException();
+
+ UeberBackend B;
+ DomainInfo di;
+ if(!B.getDomainInfo(zonename, di))
+ throw ApiException("Could not find domain '"+zonename+"'");
+
+ if(di.masters.empty())
+ throw ApiException("Domain '"+zonename+"' is not a slave domain (or has no master defined)");
+
+ random_shuffle(di.masters.begin(), di.masters.end());
+ Communicator.addSuckRequest(zonename, di.masters.front());
+ resp->body = returnJsonMessage("Added retrieval request for '"+zonename+"' from master "+di.masters.front());
+}
+
+static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
+ string zonename = apiZoneIdToName(req->parameters["id"]);
+
+ if(req->method != "PUT")
+ throw HttpMethodNotAllowedException();
+
+ UeberBackend B;
+ DomainInfo di;
+ if(!B.getDomainInfo(zonename, di))
+ throw ApiException("Could not find domain '"+zonename+"'");
+
+ if(!Communicator.notifyDomain(zonename))
+ throw ApiException("Failed to add to the queue - see server log");
+
+ resp->body = returnJsonMessage("Notification queued");
+}
+
static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
if (rr.qtype.getCode() == QType::A) {
uint32_t ip;
resp->setBody(doc);
}
-void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
-{
- string command;
-
- if(req->getvars.count("command")) {
- command = req->getvars["command"];
- req->getvars.erase("command");
- }
+void apiServerFlushCache(HttpRequest* req, HttpResponse* resp) {
+ if(req->method != "PUT")
+ throw HttpMethodNotAllowedException();
- if(command == "flush-cache") {
- extern PacketCache PC;
- int number;
- if(req->getvars["domain"].empty())
- number = PC.purge();
- else
- number = PC.purge(req->getvars["domain"]);
-
- map<string, string> object;
- object["number"]=lexical_cast<string>(number);
- //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
- resp->body = returnJsonObject(object);
- resp->status = 200;
- return;
- }
- else if(command == "pdns-control") {
- if(req->method!="POST")
- throw HttpMethodNotAllowedException();
- // cout<<"post: "<<post<<endl;
- rapidjson::Document document;
- req->json(document);
- // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
- vector<string> parameters;
- stringtok(parameters, document["parameters"].GetString(), " \t");
-
- DynListener::g_funk_t* ptr=0;
- if(!parameters.empty())
- ptr = DynListener::getFunc(toUpper(parameters[0]));
- map<string, string> m;
-
- if(ptr) {
- resp->status = 200;
- m["result"] = (*ptr)(parameters, 0);
- } else {
- resp->status = 404;
- m["error"]="No such function "+toUpper(parameters[0]);
- }
- resp->body = returnJsonObject(m);
- return;
- }
- else if(command=="log-grep") {
- // legacy parameter name hack
- req->getvars["q"] = req->getvars["needle"];
- apiServerSearchLog(req, resp);
- return;
- }
+ extern PacketCache PC;
+ int count;
+ if(req->getvars["domain"].empty())
+ count = PC.purge();
+ else
+ count = PC.purge(req->getvars["domain"]);
- resp->body = returnJsonError("No or unknown command given");
- resp->status = 404;
- return;
+ map<string, string> object;
+ object["count"] = lexical_cast<string>(count);
+ object["result"] = "Flushed cache.";
+ resp->body = returnJsonObject(object);
}
void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
try {
if(::arg().mustDo("experimental-json-interface")) {
d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
+ d_ws->registerApiHandler("/servers/localhost/flush-cache", &apiServerFlushCache);
d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
+ d_ws->registerApiHandler("/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
d_ws->registerApiHandler("/servers/localhost/zones/<id>/export", &apiServerZoneExport);
+ d_ws->registerApiHandler("/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
d_ws->registerApiHandler("/servers", &apiServer);
- // legacy dispatch
- d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
}
d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
resp->setBody(doc);
}
+static void apiServerFlushCache(HttpRequest* req, HttpResponse* resp) {
+ if(req->method != "PUT")
+ throw HttpMethodNotAllowedException();
+
+ string canon = toCanonic("", req->getvars["domain"]);
+ int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
+ count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
+ map<string, string> object;
+ object["count"] = lexical_cast<string>(count);
+ object["result"] = "Flushed cache.";
+ resp->body = returnJsonObject(object);
+}
+
RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
{
RecursorControlParser rcp; // inits
// legacy dispatch
d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
+ d_ws->registerApiHandler("/servers/localhost/flush-cache", &apiServerFlushCache);
d_ws->registerApiHandler("/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
}
map<string, string> stats;
- if(command == "domains") {
- Document doc;
- doc.SetArray();
- BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) {
- Value jzone;
- jzone.SetObject();
-
- const SyncRes::AuthDomain& zone = val.second;
- Value zonename(val.first.c_str(), doc.GetAllocator());
- jzone.AddMember("name", zonename, doc.GetAllocator());
- jzone.AddMember("type", "Zone", doc.GetAllocator());
- jzone.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
- Value servers;
- servers.SetArray();
- BOOST_FOREACH(const ComboAddress& server, zone.d_servers) {
- Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
- servers.PushBack(value, doc.GetAllocator());
- }
- jzone.AddMember("servers", servers, doc.GetAllocator());
- bool rdbit = zone.d_servers.empty() ? false : zone.d_rdForward;
- jzone.AddMember("rdbit", rdbit, doc.GetAllocator());
-
- doc.PushBack(jzone, doc.GetAllocator());
- }
- resp->setBody(doc);
- return;
- }
- else if(command == "zone") {
- string arg_zone = req->getvars["zone"];
- SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(arg_zone);
- if (ret != t_sstorage->domainmap->end()) {
- Document doc;
- doc.SetObject();
- Value root;
- root.SetObject();
-
- const SyncRes::AuthDomain& zone = ret->second;
- Value zonename(ret->first.c_str(), doc.GetAllocator());
- root.AddMember("name", zonename, doc.GetAllocator());
- root.AddMember("type", "Zone", doc.GetAllocator());
- root.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
- Value servers;
- servers.SetArray();
- BOOST_FOREACH(const ComboAddress& server, zone.d_servers) {
- Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
- servers.PushBack(value, doc.GetAllocator());
- }
- root.AddMember("servers", servers, doc.GetAllocator());
- bool rdbit = zone.d_servers.empty() ? false : zone.d_rdForward;
- root.AddMember("rdbit", rdbit, doc.GetAllocator());
-
- Value records;
- records.SetArray();
- BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) {
- Value object;
- object.SetObject();
- Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
- object.AddMember("name", jname, doc.GetAllocator());
- Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
- object.AddMember("type", jtype, doc.GetAllocator());
- object.AddMember("ttl", rr.ttl, doc.GetAllocator());
- Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
- object.AddMember("content", jcontent, doc.GetAllocator());
- records.PushBack(object, doc.GetAllocator());
- }
- root.AddMember("records", records, doc.GetAllocator());
-
- doc.AddMember("zone", root, doc.GetAllocator());
- resp->setBody(doc);
- return;
- } else {
- resp->body = returnJsonError("Could not find domain '"+arg_zone+"'");
- return;
- }
- }
- else if(command == "flush-cache") {
- string canon=toCanonic("", req->getvars["domain"]);
- int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
- count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
- stats["number"]=lexical_cast<string>(count);
- resp->body = returnJsonObject(stats);
- return;
- }
- else if(command == "get-query-ring") {
+ if(command == "get-query-ring") {
typedef pair<string,uint16_t> query_t;
vector<query_t> queries;
bool filter=!req->getvars["public-filtered"].empty();
doc.AddMember("entries", entries, doc.GetAllocator());
resp->setBody(doc);
return;
- }
-
- else if(command == "config") {
- vector<string> items = ::arg().list();
- BOOST_FOREACH(const string& var, items) {
- stats[var] = ::arg()[var];
- }
- resp->body = returnJsonObject(stats);
- return;
- }
- else if(command == "log-grep") {
- // legacy parameter name hack
- req->getvars["q"] = req->getvars["needle"];
- apiServerSearchLog(req, resp);
- return;
- }
- else if(command == "stats") {
- stats = getAllStatsMap();
- resp->body = returnJsonObject(stats);
- return;
} else {
resp->status = 404;
resp->body = returnJsonError("Command '"+command+"' not found");
self.assert_success_json(r)
data = dict([(r['name'], r['value']) for r in r.json()])
self.assertIn('uptime', data)
+
+ def test_flush_cache(self):
+ r = self.session.put(self.url("/servers/localhost/flush-cache?domain=example.org."))
+ self.assert_success_json(r)
+ data = r.json()
+ self.assertIn('count', data)
+
+ def test_flush_complete_cache(self):
+ r = self.session.put(self.url("/servers/localhost/flush-cache"))
+ self.assert_success_json(r)
+ data = r.json()
+ self.assertIn('count', data)
+ self.assertEqual(data['result'], 'Flushed cache.')
r = self.session.delete(self.url("/servers/localhost/zones/" + data['id']))
r.raise_for_status()
+ def test_retrieve_slave_zone(self):
+ payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
+ print "payload:", payload
+ print "data:", data
+ r = self.session.put(self.url("/servers/localhost/zones/" + data['id'] + "/axfr-retrieve"))
+ data = r.json()
+ print "status for axfr-retrieve:", data
+ self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
+ '\' from master 127.0.0.2')
+
+ def test_notify_master_zone(self):
+ payload, data = self.create_zone(kind='Master')
+ print "payload:", payload
+ print "data:", data
+ r = self.session.put(self.url("/servers/localhost/zones/" + data['id'] + "/notify"))
+ data = r.json()
+ print "status for notify:", data
+ self.assertEqual(data['result'], 'Notification queued')
+
def test_get_zone_with_symbols(self):
payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
name = payload['name']