From: Christian Hofstaedtler Date: Tue, 28 Jan 2014 21:25:59 +0000 (+0100) Subject: webserver: add PATCH /../zones//rrset X-Git-Tag: rec-3.6.0-rc1~213^2~1 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b3905a3dc46c4019c8968a179d91d86a463cf3fe;p=pdns webserver: add PATCH /../zones//rrset (And drop the old /jsonstat code for this.) --- diff --git a/pdns/ws.cc b/pdns/ws.cc index 4832da300..6c0e2ab0b 100644 --- a/pdns/ws.cc +++ b/pdns/ws.cc @@ -646,6 +646,77 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { resp->body = getZone(zonename); } +static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { + if(req->method != "PATCH") + throw HttpMethodNotAllowedException(); + + UeberBackend B; + DomainInfo di; + string zonename = req->path_parameters["id"]; + if(!B.getDomainInfo(zonename, di)) + throw ApiException("Could not find domain '"+zonename+"'"); + + SOAData sd; + sd.db = (DNSBackend*)-1; + if(!B.getSOA(zonename, sd) || !sd.db) + throw ApiException("Could not find domain '"+zonename+"'"); + + Document document; + parseJsonBody(req, document); + + string qname, changetype; + QType qtype; + qname = stringFromJson(document, "name"); + qtype = stringFromJson(document, "type"); + changetype = toUpper(stringFromJson(document, "changetype")); + + if (changetype == "DELETE") { + // delete all matching qname/qtype RRs + sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector()); + } + else if (changetype == "REPLACE") { + DNSResourceRecord rr; + vector rrset; + const Value& records = document["records"]; + for(SizeType idx = 0; idx < records.Size(); ++idx) { + const Value& record = records[idx]; + rr.qname = stringFromJson(record, "name"); + rr.content = stringFromJson(record, "content"); + rr.qtype = stringFromJson(record, "type"); + rr.domain_id = sd.domain_id; + rr.auth = 1; + rr.ttl = intFromJson(record, "ttl"); + rr.priority = intFromJson(record, "priority"); + + rrset.push_back(rr); + + if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV) + rr.content = lexical_cast(rr.priority)+" "+rr.content; + + try { + shared_ptr drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content)); + string tmp = drc->serialize(rr.qname); + } + catch(std::exception& e) + { + throw ApiException("Record "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what()); + } + } + // Actually store the change. + sd.db->startTransaction(qname); + sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset); + sd.db->commitTransaction(); + } + else + throw ApiException("Changetype not understood"); + + extern PacketCache PC; + PC.purge(qname); + + // success + resp->body = "{}"; +} + void StatWebServer::jsonstat(HttpRequest* req, HttpResponse* resp) { string command; @@ -697,101 +768,6 @@ void StatWebServer::jsonstat(HttpRequest* req, HttpResponse* resp) resp->body = returnJSONObject(m); return; } - else if(command == "zone-rest") { // http://jsonstat?command=zone-rest&rest=/powerdns.nl/www.powerdns.nl/a - vector parts; - stringtok(parts, req->parameters["rest"], "/"); - if(parts.size() != 3) { - resp->status = 400; - resp->body = returnJSONError("Could not parse rest parameter"); - return; - } - UeberBackend B; - SOAData sd; - sd.db = (DNSBackend*)-1; - if(!B.getSOA(parts[0], sd) || !sd.db) { - resp->status = 404; - resp->body = returnJSONError("Could not find domain '"+parts[0]+"'"); - return; - } - - QType qtype; - qtype=parts[2]; - string qname=parts[1]; - extern PacketCache PC; - PC.purge(qname); - // cerr<<"domain id: "<method == "GET") { - B.lookup(qtype, parts[1], 0, sd.domain_id); - - DNSResourceRecord rr; - string ret = "{ \"records\": ["; - map object; - bool first=1; - - while(B.get(rr)) { - if(!first) ret += ", "; - first=false; - object.clear(); - object["name"] = rr.qname; - object["type"] = rr.qtype.getName(); - object["ttl"] = lexical_cast(rr.ttl); - object["priority"] = lexical_cast(rr.priority); - object["content"] = rr.content; - ret+=returnJSONObject(object); - } - ret+="]}"; - resp->body = ret; - return; - } - else if(req->method=="DELETE") { - sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector()); - - } - else if(req->method=="POST") { - rapidjson::Document document; - if(document.Parse<0>(req->body.c_str()).HasParseError()) { - resp->status = 400; - resp->body = returnJSONError("Unable to parse JSON"); - return; - } - - DNSResourceRecord rr; - vector rrset; - const rapidjson::Value &records= document["records"]; - for(rapidjson::SizeType i = 0; i < records.Size(); ++i) { - const rapidjson::Value& record = records[i]; - rr.qname=record["name"].GetString(); - rr.content=record["content"].GetString(); - rr.qtype=record["type"].GetString(); - rr.domain_id = sd.domain_id; - rr.auth=1; - rr.ttl=intFromJson(record, "ttl"); - rr.priority=intFromJson(record, "priority"); - - rrset.push_back(rr); - - if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV) - rr.content = lexical_cast(rr.priority)+" "+rr.content; - - try { - shared_ptr drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content)); - string tmp=drc->serialize(rr.qname); - } - catch(std::exception& e) - { - resp->body = returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what()); - return; - } - } - // but now what - sd.db->startTransaction(qname); - sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset); - sd.db->commitTransaction(); - resp->body = req->body; - return; - } - } else if(command=="log-grep") { resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns["); return; @@ -876,6 +852,7 @@ void StatWebServer::launch() registerApiHandler("/servers/localhost/config", &apiServerConfig); registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog); registerApiHandler("/servers/localhost/statistics", &apiServerStatistics); + registerApiHandler("/servers/localhost/zones//rrset", &apiServerZoneRRset); registerApiHandler("/servers/localhost/zones/", &apiServerZoneDetail); registerApiHandler("/servers/localhost/zones", &apiServerZones); registerApiHandler("/servers/localhost", &apiServerDetail); diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index 816f71de9..efe7f659d 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -105,3 +105,78 @@ class Servers(ApiTestCase): for k in payload.keys(): self.assertIn(k, data) self.assertEquals(data[k], payload[k]) + + def test_ZoneRRUpdate(self): + # create + name = unique_zone_name() + payload = { + 'name': name, + 'kind': 'Native', + 'nameservers': ['ns1.foo.com', 'ns2.foo.com'] + } + r = self.session.post( + self.url("/servers/localhost/zones"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + # do a replace (= update) + payload = { + 'changetype': 'replace', + 'name': name, + 'type': 'NS', + 'records': [ + { + "name": name, + "type": "NS", + "priority": 0, + "ttl": 3600, + "content": "ns1.bar.com" + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + # verify that (only) the new record is there + r = self.session.get( + self.url("/servers/localhost/zones/" + name), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + data = r.json()['records'] + recs = [rec for rec in data if rec['type'] == payload['type'] and rec['name'] == payload['name']] + self.assertEquals(recs, payload['records']) + + def test_ZoneRRDelete(self): + # create + name = unique_zone_name() + payload = { + 'name': name, + 'kind': 'Native', + 'nameservers': ['ns1.foo.com', 'ns2.foo.com'] + } + r = self.session.post( + self.url("/servers/localhost/zones"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + # do a delete of all NS records (these are created with the zone) + payload = { + 'changetype': 'delete', + 'name': name, + 'type': 'NS' + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + # verify that the records are gone + r = self.session.get( + self.url("/servers/localhost/zones/" + name), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + data = r.json()['records'] + recs = [rec for rec in data if rec['type'] == payload['type'] and rec['name'] == payload['name']] + self.assertEquals(recs, [])