]> granicus.if.org Git - pdns/commitdiff
Now adds parameters to calculateSOASerial
authorAki Tuomi <cmouse@cmouse.fi>
Sun, 2 Jun 2013 16:55:24 +0000 (19:55 +0300)
committerAki Tuomi <cmouse@desteem.org>
Wed, 5 Jun 2013 05:03:28 +0000 (08:03 +0300)
Test driver for remotebackend

Unit test helper for remotebackend tests

Ignore test_remotebackend

Refactored code with split pipe and http

Metadata testing

Added new binaries to ignore

Fixed metadata handling

Fixed domain metadata tests

Added Makefile var REMOTEBACKEND_HTTP for test suite

Support for automation with or without http connector

Only compile libtestremotebackend.la when testing

Do not use libremotebackend.la on test compilation

Domainkeys and getBeforeAndAfterNamesAbsolute

Key can now be marked (in)active with int or boolean

Fixed wrong method name in removeDomainKey

Retain test command exit value

domain key handling tests

Two test keys for test purposes

moved doesDNSSEC before tests that depends on it

Fixed make dist

Fixed setNotified to actually send the new serial

setNotified support

setNotified support

Fixed /dns/ into /dns

Tests for all non-slave methods

Fixed rapidjson usage on superMaster and feedRecord

Change array handling to array[foo] format

Change json2string to return true/false whether conversion succeeded

Added SOA, NS, A record content registrations for helpers

Fixed createSlaveDomain to do POST

superMasterBackends sets ddb = 0 when failure

createSlaveDomain support

supermaster and createslavedomain support

domain_id handling fixed, now uses post for transactions

feedRecord stores rr in parameter rr

Fix various url patterns

Rest of the methods

15 files changed:
configure.ac
modules/remotebackend/.gitignore [new file with mode: 0644]
modules/remotebackend/Makefile.am
modules/remotebackend/httpconnector.cc
modules/remotebackend/regression-tests/dnsbackend.rb
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend-http.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend-keys.hh [new file with mode: 0644]
modules/remotebackend/test-remotebackend-pipe.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend.cc [new file with mode: 0644]
modules/remotebackend/testrunner.sh [new file with mode: 0755]
modules/remotebackend/unittest.rb [new file with mode: 0644]
modules/remotebackend/unittest_http.rb [new file with mode: 0755]
modules/remotebackend/unittest_pipe.rb [new file with mode: 0755]

index 04e0d70d98ec86925b4a9683e5d75257804eb45a..d8c37ccf15099ff534ef412c88e5aec713657007 100644 (file)
@@ -255,10 +255,12 @@ AM_CONDITIONAL(REMOTEBACKEND_HTTP,test x"$enable_remotebackend_http" = "xyes")
 if test "x$enable_remotebackend_http" = "xyes"
 then
         PKG_CHECK_MODULES(LIBCURL, libcurl, HAVE_LIBCURL=yes, AC_MSG_ERROR([Could not find libcurl]))
+        REMOTEBACKEND_HTTP=yes
         AC_SUBST(LIBCURL_LIBS)
         AC_SUBST(LIBCURL_CFLAGS)
         AC_DEFINE(HAVE_LIBCURL,1,[If we have libcurl])
        AC_DEFINE(REMOTEBACKEND_HTTP,1,[If we want HTTP connector])
+        AC_SUBST(REMOTEBACKEND_HTTP)
 fi
 
 AC_MSG_CHECKING(whether we should build static binaries)
diff --git a/modules/remotebackend/.gitignore b/modules/remotebackend/.gitignore
new file mode 100644 (file)
index 0000000..6b621dc
--- /dev/null
@@ -0,0 +1,3 @@
+remotebackend-access.log
+test_remotebackend_http
+test_remotebackend_pipe
index a47f8bbae6ff8b846e24813c1d633ba84eace8f4..6ee7318a4fe360d86f534ef4acdccbf7684190a7 100644 (file)
@@ -5,9 +5,46 @@ AM_CPPFLAGS=@THREADFLAGS@ $(BOOST_CPPFLAGS) $(LIBCURL_CFLAGS) -I../../pdns/ext/r
 #endif
 
 EXTRA_DIST=OBJECTFILES OBJECTLIBS
-lib_LTLIBRARIES = libremotebackend.la
+EXTRA_PROGRAMS=test_remotebackend_pipe test_remotebackend_http
+EXTRA_LTLIBRARIES=libtestremotebackend.la
+
+lib_LTLIBRARIES = libremotebackend.la 
 
 libremotebackend_la_SOURCES=remotebackend.hh remotebackend.cc unixconnector.cc httpconnector.cc pipeconnector.cc 
 
 libremotebackend_la_LDFLAGS=-module -avoid-version
 libremotebackend_la_LIBS=$(LIBCURL_LIBS)
+
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message REMOTEBACKEND_HTTP=$(REMOTEBACKEND_HTTP) ./testrunner.sh 
+TESTS=test_remotebackend_pipe test_remotebackend_http
+
+BUILT_SOURCES=../../pdns/dnslabeltext.cc
+
+../../pdns/dnslabeltext.cc: ../../pdns/dnslabeltext.rl
+       make -C ../../pdns dnslabeltext.cc
+
+libtestremotebackend_la_SOURCES=../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc ../../pdns/ueberbackend.hh ../../pdns/ueberbackend.cc \
+        ../../pdns/nameserver.cc ../../pdns/misc.cc ../../pdns/arguments.hh \
+        ../../pdns/unix_utility.cc ../../pdns/logger.cc ../../pdns/statbag.cc ../../pdns/arguments.hh ../../pdns/arguments.cc ../../pdns/qtype.cc ../../pdns/dnspacket.cc \
+        ../../pdns/dnswriter.cc ../../pdns/base64.cc ../../pdns/base32.cc ../../pdns/dnsrecords.cc ../../pdns/dnslabeltext.cc ../../pdns/dnsparser.cc \
+        ../../pdns/rcpgenerator.cc ../../pdns/ednssubnet.cc ../../pdns/nsecrecords.cc ../../pdns/sillyrecords.cc ../../pdns/dnssecinfra.cc \
+        ../../pdns/aes/dns_random.cc ../../pdns/packetcache.hh ../../pdns/packetcache.cc \
+        ../../pdns/aes/aescpp.h ../../pdns/dns.hh ../../pdns/dns.cc ../../pdns/json.hh ../../pdns/json.cc \
+        ../../pdns/aes/aescrypt.c ../../pdns/aes/aes.h ../../pdns/aes/aeskey.c ../../pdns/aes/aes_modes.c ../../pdns/aes/aesopt.h \
+        ../../pdns/aes/aestab.c ../../pdns/aes/aestab.h ../../pdns/aes/brg_endian.h ../../pdns/aes/brg_types.h ../pipebackend/coprocess.cc \
+        remotebackend.hh remotebackend.cc unixconnector.cc httpconnector.cc pipeconnector.cc
+
+libtestremotebackend_la_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+libtestremotebackend_la_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+
+test_remotebackend_pipe_SOURCES=test-remotebackend.cc test-remotebackend-pipe.cc 
+
+test_remotebackend_http_SOURCES=test-remotebackend.cc test-remotebackend-http.cc ../../config.h
+
+test_remotebackend_pipe_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_pipe_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_pipe_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
+
+test_remotebackend_http_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_http_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_http_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
index c5b1a1673cf1fe5bdc55662a957ef6c532c3b23d..b52d858cb81885cc3505d5fc56128bfcf6323f92 100644 (file)
@@ -40,12 +40,13 @@ size_t httpconnector_write_data(void *buffer, size_t size, size_t nmemb, void *u
 }
 
 // converts json value into string
-void HTTPConnector::json2string(const rapidjson::Value &input, std::string &output) {
+bool HTTPConnector::json2string(const rapidjson::Value &input, std::string &output) {
    if (input.IsString()) output = input.GetString();
    else if (input.IsNull()) output = "";
    else if (input.IsUint()) output = lexical_cast<std::string>(input.GetUint());
    else if (input.IsInt()) output = lexical_cast<std::string>(input.GetInt());
-   else output = "inconvertible value";
+   else return false;
+   return true;
 }
 
 void HTTPConnector::addUrlComponent(const rapidjson::Value &parameters, const char *element, std::stringstream& ss) {
@@ -104,16 +105,13 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     // id must be first due to the fact that the qname/name can be empty
 
     addUrlComponent(parameters, "id", ss);
+    addUrlComponent(parameters, "domain_id", ss);
     addUrlComponent(parameters, "zonename", ss);
     addUrlComponent(parameters, "qname", ss);
     addUrlComponent(parameters, "name", ss);
     addUrlComponent(parameters, "kind", ss);
     addUrlComponent(parameters, "qtype", ss);
 
-    // finally add suffix
-    ss << d_url_suffix;
-    curl_easy_setopt(d_c, CURLOPT_URL, ss.str().c_str());
-    
     (*slist) = NULL;
     // set the correct type of request based on method
     if (method == "activateDomainKey" || method == "deactivateDomainKey") { 
@@ -150,11 +148,18 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     } else if (method == "createSlaveDomain") {
         addUrlComponent(parameters, "ip", ss);
         addUrlComponent(parameters, "domain", ss);
-        addUrlComponent(parameters, "account", ss);
+        if (parameters.HasMember("account")) {
+           std::string out = parameters["account"].GetString();
+           curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+           curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str()); 
+        } else {
+           curl_easy_setopt(d_c, CURLOPT_POST, 1);
+           curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0); 
+        }
     } else if (method == "replaceRRSet") {
         std::stringstream ss2;
         size_t index = 0;
-        for(rapidjson::Value::ConstValueIterator itr = parameters["nsset"].Begin(); itr != parameters["nsset"].End(); itr++) {
+        for(rapidjson::Value::ConstValueIterator itr = parameters["rrset"].Begin(); itr != parameters["rrset"].End(); itr++) {
             index++;
             ss2 << buildMemberListArgs("rrset[" + boost::lexical_cast<std::string>(index) + "]", itr, d_c);
         }
@@ -191,13 +196,18 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     } else if (method == "startTransaction") {
         addUrlComponent(parameters, "domain", ss);
         addUrlComponent(parameters, "trxid", ss);
+        curl_easy_setopt(d_c, CURLOPT_POST, 1);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0);
     } else if (method == "commitTransaction" || method == "abortTransaction") {
         addUrlComponent(parameters, "trxid", ss);
+        curl_easy_setopt(d_c, CURLOPT_POST, 1);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0);
     } else if (method == "calculateSOASerial") {
         addUrlComponent(parameters, "domain", ss);
         std::string out = buildMemberListArgs("sd", &parameters["sd"], d_c);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
     } else if (method == "setDomainMetadata") {
-        int n=0;
         // copy all metadata values into post
         std::stringstream ss2;
         const rapidjson::Value& param = parameters["value"];
@@ -205,7 +215,7 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
         // this one has values too
         if (param.IsArray()) {
            for(rapidjson::Value::ConstValueIterator i = param.Begin(); i != param.End(); i++) {
-              ss2 << "value" << (++n) << "=" << i->GetString() << "&";
+              ss2 << "value[]=" << i->GetString() << "&";
            }
         }
         sparam = ss2.str();
@@ -214,6 +224,12 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     } else if (method == "removeDomainKey") {
         // this one is delete
         curl_easy_setopt(d_c, CURLOPT_CUSTOMREQUEST, "DELETE");
+    } else if (method == "setNotified") {
+        tmpstr = (char*)malloc(128);
+        snprintf(tmpstr, 128, "serial=%u", parameters["serial"].GetInt());
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, strlen(tmpstr));
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, tmpstr);
+        free(tmpstr);
     } else {
         // perform normal get
         curl_easy_setopt(d_c, CURLOPT_HTTPGET, 1);
@@ -228,11 +244,16 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
           !strncmp(member,"name",4) || !strncmp(member,"kind",4) ||
           !strncmp(member,"qtype",5) || !strncmp(member,"id",2) ||
           !strncmp(member,"key",3)) continue;
-      json2string(parameters[member], sparam);
-      snprintf(header, sizeof header, "X-RemoteBackend-%s: %s", iter->name.GetString(), sparam.c_str());
-      (*slist) = curl_slist_append((*slist), header);
+      if (json2string(parameters[member], sparam)) {
+         snprintf(header, sizeof header, "X-RemoteBackend-%s: %s", iter->name.GetString(), sparam.c_str());
+         (*slist) = curl_slist_append((*slist), header);
+      }
     };
 
+    // finally add suffix and store url
+    ss << d_url_suffix;
+    curl_easy_setopt(d_c, CURLOPT_URL, ss.str().c_str());
+
     // store headers into request
     curl_easy_setopt(d_c, CURLOPT_HTTPHEADER, *slist); 
 }
index dbf9e95835459006c1de3ab1c88b1720a091891e..aaac5a5b7f98859b07f0846c69a0f9f94803eb15 100644 (file)
@@ -8,6 +8,28 @@ class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
      @f = File.open("/tmp/tmp.txt","a")
    end
 
+   def parse_arrays(params)
+     newparams = {}
+     params.each do |key,val|
+         if key=~/^(.*)\[(.*)\]\[(.*)\]/
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = {} unless newparams[$1].has_key? $2
+             newparams[$1][$2][$3] = val
+             params.delete key
+         elsif key=~/^(.*)\[(.*)\]/
+           if $2 == ""
+             newparams[$1] = [] unless newparams.has_key? $1
+             newparams[$1] << val
+           else
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = val
+           end
+           params.delete key
+         end
+     end
+     params.merge newparams
+   end
+
    def parse_url(url)
      url = url.split('/')
      method = url.shift.downcase
@@ -75,13 +97,8 @@ class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
         }
      end
 
-     if method == "do_setdomainmetadata"
-        args["value"] = []
-        args.each do |k,a|
-            args["value"] << a if k[/^value/]
-        end
-     end
-
+     args = parse_arrays args
      @f.puts method
      @f.puts args
 
index c6b58bc1ce5efff5c990702e42f3c15540b7ff9f..e81a6f3c8553f34f5fb6f775adae95f102e739d8 100644 (file)
@@ -331,7 +331,10 @@ bool RemoteBackend::getDomainKeys(const std::string& name, unsigned int kind, st
       DNSBackend::KeyData key;
       key.id = (*iter)["id"].GetUint();
       key.flags = (*iter)["flags"].GetUint();
-      key.active = (*iter)["active"].GetBool();
+      if ((*iter)["active"].IsBool())
+         key.active = (*iter)["active"].GetBool();
+      else 
+         key.active = ((*iter)["active"].GetInt() != 0 ? true : false ); // case where it's returned as non-boolean
       key.content = (*iter)["content"].GetString();
       keys.push_back(key);
    }
@@ -346,7 +349,7 @@ bool RemoteBackend::removeDomainKey(const string& name, unsigned int id) {
    if (d_dnssec == false) return false;
 
    query.SetObject();
-   JSON_ADD_MEMBER(query, "method", "getDomainKeys", query.GetAllocator());
+   JSON_ADD_MEMBER(query, "method", "removeDomainKey", query.GetAllocator());
    parameters.SetObject();
    JSON_ADD_MEMBER(parameters, "name", name.c_str(), query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "id", id, query.GetAllocator());
@@ -503,7 +506,7 @@ void RemoteBackend::setNotified(uint32_t id, uint32_t serial) {
    JSON_ADD_MEMBER(query, "method", "setNotified", query.GetAllocator());
    parameters.SetObject();
    JSON_ADD_MEMBER(parameters, "id", id, query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "serial", id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "serial", serial, query.GetAllocator());
    query.AddMember("parameters", parameters, query.GetAllocator());
  
    if (connector->send(query) == false || connector->recv(answer) == false) {
@@ -525,7 +528,7 @@ bool RemoteBackend::superMasterBackend(const string &ip, const string &domain, c
    rrset.SetArray();
    rrset.Reserve(nsset.size(), query.GetAllocator());
    for(rapidjson::SizeType i = 0; i < nsset.size(); i++) {
-      rapidjson::Value &rr = rrset[i]; 
+      rapidjson::Value rr;
       rr.SetObject();
       JSON_ADD_MEMBER(rr, "qtype", nsset[i].qtype.getName().c_str(), query.GetAllocator());
       JSON_ADD_MEMBER(rr, "qname", nsset[i].qname.c_str(), query.GetAllocator());
@@ -534,10 +537,13 @@ bool RemoteBackend::superMasterBackend(const string &ip, const string &domain, c
       JSON_ADD_MEMBER(rr, "ttl", nsset[i].ttl, query.GetAllocator());
       JSON_ADD_MEMBER(rr, "priority", nsset[i].priority, query.GetAllocator());
       JSON_ADD_MEMBER(rr, "auth", nsset[i].auth, query.GetAllocator());
+      rrset.PushBack(rr, query.GetAllocator());
    }
    parameters.AddMember("nsset", rrset, query.GetAllocator());
    query.AddMember("parameters", parameters, query.GetAllocator());
 
+   *ddb = 0;
+
    if (connector->send(query) == false || connector->recv(answer) == false)
      return false;
 
@@ -577,11 +583,13 @@ bool RemoteBackend::replaceRRSet(uint32_t domain_id, const string& qname, const
    JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "qname", qname.c_str(), query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "qtype", qtype.getName().c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
    rj_rrset.SetArray();
    rj_rrset.Reserve(rrset.size(), query.GetAllocator());
 
    for(rapidjson::SizeType i = 0; i < rrset.size(); i++) {
-      rapidjson::Value &rr = rj_rrset[i];
+      rapidjson::Value rr;
       rr.SetObject();
       JSON_ADD_MEMBER(rr, "qtype", rrset[i].qtype.getName().c_str(), query.GetAllocator());
       JSON_ADD_MEMBER(rr, "qname", rrset[i].qname.c_str(), query.GetAllocator());
@@ -590,6 +598,7 @@ bool RemoteBackend::replaceRRSet(uint32_t domain_id, const string& qname, const
       JSON_ADD_MEMBER(rr, "ttl", rrset[i].ttl, query.GetAllocator());
       JSON_ADD_MEMBER(rr, "priority", rrset[i].priority, query.GetAllocator());
       JSON_ADD_MEMBER(rr, "auth", rrset[i].auth, query.GetAllocator());
+      rj_rrset.PushBack(rr, query.GetAllocator());
    }
    parameters.AddMember("rrset", rj_rrset, query.GetAllocator());
    query.AddMember("parameters", parameters, query.GetAllocator());
@@ -602,20 +611,26 @@ bool RemoteBackend::replaceRRSet(uint32_t domain_id, const string& qname, const
 
 bool RemoteBackend::feedRecord(const DNSResourceRecord &rr, string *ordername) {
    rapidjson::Document query,answer;
-   rapidjson::Value parameters;
+   rapidjson::Value parameters,rj_rr;
    query.SetObject();
    JSON_ADD_MEMBER(query, "method", "feedRecord", query.GetAllocator());
    parameters.SetObject();
-   JSON_ADD_MEMBER(parameters, "qtype", rr.qtype.getName().c_str(), query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "qname", rr.qname.c_str(), query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "qclass", QClass::IN, query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "content", rr.content.c_str(), query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "ttl", rr.ttl, query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "priority", rr.priority, query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "auth", rr.auth, query.GetAllocator());
+   rj_rr.SetObject();
+   JSON_ADD_MEMBER(rj_rr, "qtype", rr.qtype.getName().c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "qname", rr.qname.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "qclass", QClass::IN, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "content", rr.content.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "ttl", rr.ttl, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "priority", rr.priority, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "auth", rr.auth, query.GetAllocator());
+   parameters.AddMember("rr", rj_rr, query.GetAllocator());
+
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
    if (ordername) {
      JSON_ADD_MEMBER(parameters, "ordername", ordername->c_str(), query.GetAllocator());
    }
+
    query.AddMember("parameters", parameters, query.GetAllocator());
 
    if (connector->send(query) == false || connector->recv(answer) == false)
@@ -631,6 +646,7 @@ bool RemoteBackend::feedEnts(int domain_id, set<string>& nonterm) {
    JSON_ADD_MEMBER(query, "method", "feedEnts", query.GetAllocator());
    parameters.SetObject();
    JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
    nts.SetArray();
    BOOST_FOREACH(const string &t, nonterm) {
       nts.PushBack(t.c_str(), query.GetAllocator());
@@ -655,6 +671,7 @@ bool RemoteBackend::feedEnts3(int domain_id, const string &domain, set<string> &
    JSON_ADD_MEMBER(parameters, "times", times, query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "salt", salt.c_str(), query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "narrow", narrow, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
 
    nts.SetArray();
    BOOST_FOREACH(const string &t, nonterm) {
index 8984aa089f982421c1d5520bdc65d6b752eb1c23..9e7d052f6576ffaf036a7b139fdf5061f4c3befc 100644 (file)
@@ -66,7 +66,7 @@ class HTTPConnector: public Connector {
     CURL *d_c;
     std::string d_data;
     int timeout;
-    void json2string(const rapidjson::Value &input, std::string &output);
+    bool json2string(const rapidjson::Value &input, std::string &output);
     void requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist);
     void addUrlComponent(const rapidjson::Value &parameters, const char *element, std::stringstream& ss);
 };
diff --git a/modules/remotebackend/test-remotebackend-http.cc b/modules/remotebackend/test-remotebackend-http.cc
new file mode 100644 (file)
index 0000000..f91d05a
--- /dev/null
@@ -0,0 +1,73 @@
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+#ifdef REMOTEBACKEND_HTTP
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="http:url=http://localhost:62434/dns";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
+#else
+
+#include <iostream>
+
+int main(void) {
+  std::cout << "No HTTP support in remotebackend - skipping test" << std::endl;
+  return 0;
+}
+
+#endif
diff --git a/modules/remotebackend/test-remotebackend-keys.hh b/modules/remotebackend/test-remotebackend-keys.hh
new file mode 100644 (file)
index 0000000..2ccc9b4
--- /dev/null
@@ -0,0 +1,3 @@
+DNSBackend::KeyData k1 = {1, 257, true, std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: qpe9fxlN4dBT38cLPWtqljZhcJjbqRprj9XsYmf2/uFu4kA5sHYrlQY7H9lpzGJPRfOAfxShBpKs1AVaVInfJQ==\nPublicExponent: AQAB\nPrivateExponent: Ad3YogzXvVDLsWuAfioY571QlolbdTbzVlhLEMLD6dSRx+xcZgw6c27ak2HAH00iSKTvqK3AyeaK8Eqy/oJ5QQ==\nPrime1: wo8LZrdU2y0xLGCeLhwziQDDtTMi18NEIwlx8tUPnhs=\nPrime2: 4HcuFqgo7NOiXFvN+V2PT+QaIt2+oi6D2m8/qtTDS78=\nExponent1: GUdCoPbi9JM7l1t6Ud1iKMPLqchaF5SMTs0UXAuous8=\nExponent2: nzgKqimX9f1corTAEw0pddrwKyEtcu8ZuhzFhZCsAxM=\nCoefficient: YGNxbulf5GTNiIu0oNKmAF0khNtx9layjOPEI0R4/RY=") };
+
+DNSBackend::KeyData k2 = {2, 256, true, std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: tY2TAMgL/whZdSbn2aci4wcMqohO24KQAaq5RlTRwQ33M8FYdW5fZ3DMdMsSLQUkjGnKJPKEdN3Qd4Z5b18f+w==\nPublicExponent: AQAB\nPrivateExponent: BB6xibPNPrBV0PUp3CQq0OdFpk9v9EZ2NiBFrA7osG5mGIZICqgOx/zlHiHKmX4OLmL28oU7jPKgogeuONXJQQ==\nPrime1: yjxe/iHQ4IBWpvCmuGqhxApWF+DY9LADIP7bM3Ejf3M=\nPrime2: 5dGWTyYEQRBVK74q1a64iXgaNuYm1pbClvvZ6ccCq1k=\nExponent1: TwM5RebmWeAqerzJFoIqw5IaQugJO8hM4KZR9A4/BTs=\nExponent2: bpV2HSmu3Fvuj7jWxbFoDIXlH0uJnrI2eg4/4hSnvSk=\nCoefficient: e2uDDWN2zXwYa2P6VQBWQ4mR1ZZjFEtO/+YqOJZun1Y=") };
diff --git a/modules/remotebackend/test-remotebackend-pipe.cc b/modules/remotebackend/test-remotebackend-pipe.cc
new file mode 100644 (file)
index 0000000..9a4eb15
--- /dev/null
@@ -0,0 +1,65 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include "pdns/dnsrecords.hh"
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="pipe:command=unittest_pipe.rb";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+               // load few record types to help out
+               SOARecordContent::report();
+               NSRecordContent::report();
+                ARecordContent::report();
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
diff --git a/modules/remotebackend/test-remotebackend.cc b/modules/remotebackend/test-remotebackend.cc
new file mode 100644 (file)
index 0000000..28427c8
--- /dev/null
@@ -0,0 +1,247 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+#include "test-remotebackend-keys.hh"
+
+extern DNSBackend *be;
+
+BOOST_AUTO_TEST_SUITE(test_remotebackend_so)
+
+BOOST_AUTO_TEST_CASE(test_method_lookup) {
+   BOOST_TEST_MESSAGE("Testing lookup method");
+   DNSResourceRecord rr;
+   be->lookup(QType(QType::SOA), "unit.test");
+   // then try to get()
+   BOOST_CHECK(be->get(rr)); // and this should be TRUE.
+   // then we check rr contains what we expect
+   BOOST_CHECK_EQUAL(rr.qname, "unit.test");
+   BOOST_CHECK_MESSAGE(rr.qtype == QType::SOA, "returned qtype was not SOA");
+   BOOST_CHECK_EQUAL(rr.content, "ns.unit.test hostmaster.unit.test 1 2 3 4 5 6");
+   BOOST_CHECK_EQUAL(rr.ttl, 300);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_list) {
+   int record_count = 0;
+   DNSResourceRecord rr;
+
+   BOOST_TEST_MESSAGE("Testing list method");
+   be->list("unit.test", -1);
+   while(be->get(rr)) record_count++;
+
+   BOOST_CHECK_EQUAL(record_count, 5); // number of records our test domain has
+}
+
+BOOST_AUTO_TEST_CASE(test_method_doesDNSSEC) {
+   BOOST_TEST_MESSAGE("Testing doesDNSSEC method");
+   BOOST_CHECK(be->doesDNSSEC()); // should be true
+}
+
+BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata) {
+   std::vector<std::string> meta;
+   meta.push_back("VALUE");
+   BOOST_TEST_MESSAGE("Testing setDomainMetadata method");
+   BOOST_CHECK(be->setDomainMetadata("unit.test","TEST", meta));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
+   std::vector<std::string> meta;
+   BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
+   be->getDomainMetadata("unit.test","TEST", meta);
+   BOOST_CHECK_EQUAL(meta.size(), 1);
+   // in case we got more than one value, which would be unexpected
+   // but not fatal
+   if (meta.size() > 0)
+      BOOST_CHECK_EQUAL(meta[0], "VALUE");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_addDomainKey) {
+   BOOST_TEST_MESSAGE("Testing addDomainKey method");
+   BOOST_CHECK_EQUAL(be->addDomainKey("unit.test",k1), 1);
+   BOOST_CHECK_EQUAL(be->addDomainKey("unit.test",k2), 2);    
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainKeys) {
+   std::vector<DNSBackend::KeyData> keys;
+   BOOST_TEST_MESSAGE("Testing getDomainKeys method");
+   // we expect to get two keys
+   be->getDomainKeys("unit.test",0,keys);
+   BOOST_CHECK_EQUAL(keys.size(), 2);
+   // in case we got more than 2 keys, which would be unexpected
+   // but not fatal
+   if (keys.size() > 1) {
+      // check that we have two keys
+      BOOST_FOREACH(DNSBackend::KeyData &kd, keys) {
+        BOOST_CHECK(kd.id > 0);
+        BOOST_CHECK(kd.flags == 256 || kd.flags == 257);
+        BOOST_CHECK(kd.active == true);
+        BOOST_CHECK(kd.content.size() > 500);
+      }
+   }
+}
+
+BOOST_AUTO_TEST_CASE(test_method_deactivateDomainKey) {
+   BOOST_TEST_MESSAGE("Testing deactivateDomainKey method");
+   BOOST_CHECK(be->deactivateDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_activateDomainKey) {
+   BOOST_TEST_MESSAGE("Testing activateDomainKey method");
+   BOOST_CHECK(be->activateDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_removeDomainKey) {
+   BOOST_CHECK(be->removeDomainKey("unit.test",2));
+   BOOST_CHECK(be->removeDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute) {
+   std::string unhashed,before,after;
+   BOOST_TEST_MESSAGE("Testing getBeforeAndAfterNamesAbsolute method");
+   
+   be->getBeforeAndAfterNamesAbsolute(-1, "middle.unit.test", unhashed, before, after);
+   BOOST_CHECK_EQUAL(unhashed, "middle");
+   BOOST_CHECK_EQUAL(before, "begin");
+   BOOST_CHECK_EQUAL(after, "stop");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getTSIGKey) {
+   std::string algorithm, content;
+   BOOST_TEST_MESSAGE("Testing getTSIGKey method");
+   be->getTSIGKey("unit.test",&algorithm,&content);
+   BOOST_CHECK_EQUAL(algorithm, "NULL");
+   BOOST_CHECK_EQUAL(content, "NULL");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_setNotified) {
+   BOOST_TEST_MESSAGE("Testing setNotified method");
+   be->setNotified(1, 2);
+   BOOST_CHECK(true); // we check this on next step
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainInfo) {
+   DomainInfo di;
+   BOOST_TEST_MESSAGE("Testing getDomainInfo method");
+   be->getDomainInfo("unit.test", di);
+   BOOST_CHECK_EQUAL(di.zone, "unit.test");
+   BOOST_CHECK_EQUAL(di.serial, 2);
+   BOOST_CHECK_EQUAL(di.notified_serial, 2);
+   BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
+   BOOST_CHECK_EQUAL(di.backend, be);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_superMasterBackend) {
+   DNSResourceRecord rr;
+   std::vector<DNSResourceRecord> nsset; 
+   DNSBackend *dbd;
+   BOOST_TEST_MESSAGE("Testing superMasterBackend method");
+
+   rr.qname = "example.com";
+   rr.qtype = QType::NS;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns1.example.com";
+   nsset.push_back(rr);
+   rr.qname = "example.com";
+   rr.qtype = QType::NS;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns2.example.com";
+   nsset.push_back(rr);
+
+   BOOST_CHECK(be->superMasterBackend("10.0.0.1", "example.com", nsset, NULL, &dbd));
+
+   // let's see what we got
+   BOOST_CHECK_EQUAL(dbd, be);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_createSlaveDomain) {
+   BOOST_TEST_MESSAGE("Testing createSlaveDomain method");
+   BOOST_CHECK(be->createSlaveDomain("10.0.0.1", "pirate.unit.test", ""));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedRecord) {
+   DNSResourceRecord rr;
+   BOOST_TEST_MESSAGE("Testing feedRecord method");
+   be->startTransaction("example.com",2);
+   rr.qname = "example.com";
+   rr.qtype = QType::SOA;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns1.example.com hostmaster.example.com 2013013441 7200 3600 1209600 300";
+   BOOST_CHECK(be->feedRecord(rr, NULL));
+   rr.qname = "replace.example.com";
+   rr.qtype = QType::A;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "127.0.0.1";
+   BOOST_CHECK(be->feedRecord(rr, NULL));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_replaceRRSet) {
+   be->startTransaction("example.com",2);
+   DNSResourceRecord rr;
+   std::vector<DNSResourceRecord> rrset;
+   BOOST_TEST_MESSAGE("Testing replaceRRSet method");
+   rr.qname = "replace.example.com";
+   rr.qtype = QType::A;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "1.1.1.1";
+   rrset.push_back(rr);
+   BOOST_CHECK(be->replaceRRSet(2, "replace.example.com", QType(QType::A), rrset));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedEnts) {
+   BOOST_TEST_MESSAGE("Testing feedEnts method");
+   be->startTransaction("example.com",2);
+   set<string> nonterm = boost::assign::list_of("_udp")("_sip._udp");
+   BOOST_CHECK(be->feedEnts(2, nonterm));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedEnts3) {
+   BOOST_TEST_MESSAGE("Testing feedEnts3 method");
+   be->startTransaction("example.com",2);
+   set<string> nonterm = boost::assign::list_of("_udp")("_sip._udp");
+   BOOST_CHECK(be->feedEnts3(2, "example.com", nonterm, 1, "\xaa\xbb\xcc\xdd", 0));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_abortTransaction) {
+   BOOST_TEST_MESSAGE("Testing abortTransaction method");
+   be->startTransaction("example.com",2);
+   BOOST_CHECK(be->abortTransaction());
+}
+
+BOOST_AUTO_TEST_CASE(test_method_calculateSOASerial) {
+   SOAData sd;
+   time_t serial;
+   be->getSOA("unit.test",sd);
+   BOOST_CHECK(be->calculateSOASerial("unit.test",sd,serial));
+
+   BOOST_CHECK_EQUAL(serial, 2013060300);
+}
+
+BOOST_AUTO_TEST_SUITE_END();
diff --git a/modules/remotebackend/testrunner.sh b/modules/remotebackend/testrunner.sh
new file mode 100755 (executable)
index 0000000..2828ad0
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash 
+
+webrick_pid=""
+
+if [ x"$REMOTEBACKEND_HTTP" == "xyes" ]; then 
+
+if [ `basename "$1"` == "test_remotebackend_http" ]; then 
+ ./unittest_http.rb &  
+ webrick_pid=$!
+ sleep 1
+fi
+
+$1
+rv=$?
+
+if [ ! -z "$webrick_pid" ]; then 
+   kill -TERM $webrick_pid
+   # wait a moment for it to die 
+   i=0
+   while [ $i -lt 5 ]; do
+     sleep 1
+     kill -0 $webrick_pid 2>/dev/null
+     if [ $? -ne 0 ]; then break; fi
+     let i=i+1
+   done
+fi
+
+else
+
+$1
+rv=$?
+
+fi
+
+exit $rv
diff --git a/modules/remotebackend/unittest.rb b/modules/remotebackend/unittest.rb
new file mode 100644 (file)
index 0000000..69ae27f
--- /dev/null
@@ -0,0 +1,224 @@
+require 'rubygems'
+require 'json'
+
+# define a simple $domain
+
+$ttl = 300
+$notified_serial = 1
+
+$domain = {
+  "unit.test" => { 
+      "SOA" => ["ns.unit.test hostmaster.unit.test 1 2 3 4 5 6"],
+      "NS" => ["ns1.unit.test", "ns2.unit.test"],
+  },
+  "ns1.unit.test" => {
+       "A" => ["10.0.0.1"]
+  },
+  "ns2.unit.test" => {
+       "A" => ["10.0.0.2"]
+  }
+}
+
+$meta = {}
+
+$keys = {}
+
+class Handler
+   def initialize
+   end
+
+   def rr(qname, qtype, content, ttl, priority = 0, auth = 1, domain_id = -1)
+      {:qname => qname, :qtype => qtype, :content => content, :ttl => ttl.to_i, :priority => priority.to_i, :auth => auth.to_i, :domain_id => domain_id.to_i}
+   end
+
+   def do_initialize(*args)
+     return true, "Test bench initialized"
+   end
+
+   def do_lookup(args) 
+     ret = []
+     if $domain.has_key?(args["qname"])
+       if $domain[args["qname"]].has_key?(args["qtype"])
+         $domain[args["qname"]][args["qtype"]].each do |rd|
+            ret << rr(args["qname"], args["qtype"], rd, $ttl)
+         end
+       elsif args["qtype"] == 'ANY'
+         $domain[args["qname"]].each do |qt,qr|
+           qr.each do |rd|
+             ret << rr(args["qname"], qt, rd, $ttl)
+           end
+         end
+       end
+     end
+     [false] unless ret.size>0
+     [ret]
+   end
+
+   def do_list(args)
+     ret = []
+     if args["zonename"] == "unit.test"
+       $domain.each do |qname,rdata| 
+         rdata.each do |rtype,rc|
+          rc.each do |rd|
+            ret << rr(qname,rtype,rd,$ttl)
+          end
+         end
+        end
+     end
+     [false] unless ret.size>0
+     [ret]
+   end 
+
+   def do_getdomainmetadata(args)
+     return [ $meta[args["name"]][args["kind"]] ] if $meta.has_key?(args["name"]) and $meta[args["name"]].has_key?(args["kind"])
+     return [false]
+   end
+
+   def do_setdomainmetadata(args)
+     $meta[args["name"].to_s] = {} unless $meta.has_key? args["name"]
+     $meta[args["name"].to_s][args["kind"].to_s] = args["value"].to_a
+     [true]
+   end
+
+   def do_adddomainkey(args)
+     $keys[args["name"]] = [] unless $keys.has_key? args["name"]
+     id=$keys[args["name"]].size + 1
+     args["key"]["id"] = id
+     $keys[args["name"]] << args["key"]
+     [id]
+   end
+
+   def do_getdomainkeys(args) 
+     if $keys.has_key? args["name"]
+       return [ $keys[args["name"]] ]
+     end
+     [false]
+   end 
+
+   def do_activatedomainkey(args) 
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+         $keys[args["name"]][args["id"]-1]["active"] = true
+         return [true]
+      end
+     end
+     [false]
+   end 
+
+   def do_deactivatedomainkey(args)
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+         $keys[args["name"]][args["id"]-1]["active"] = false
+         return [true]
+      end
+     end
+     [false]
+   end
+
+   def do_removedomainkey(args)
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+       $keys[args["name"]].delete_at args["id"]-1
+       return [true]
+      end
+     end
+     [false]
+   end 
+
+   def do_getbeforeandafternamesabsolute(args)
+     return [ { :unhashed => "middle", :before => "begin", :after => "stop" } ] if args["qname"] == 'middle.unit.test'
+     [false]
+   end
+
+   def do_gettsigkey(args) 
+     if args["name"] == "unit.test"
+       return [{:algorithm => "NULL", :content => "NULL"}]
+     end
+     [false] 
+   end
+
+   def do_setnotified(args) 
+     if args["id"].to_i == 1 
+       $notified_serial = args["serial"].to_i
+       return [true]
+     end
+     [false]
+   end
+
+   def do_getdomaininfo(args) 
+     if args["name"] == "unit.test"
+       return [{ 
+               :id => 1,
+               :zone => "unit.test",
+               :masters => ["10.0.0.1"],
+               :notified_serial => $notified_serial,
+               :serial => $notified_serial, 
+               :last_check => Time.now.to_i,
+               :kind => 'native'
+       }]
+     end
+     [false]
+   end
+
+   def do_supermasterbackend(args) 
+     $domain[args["domain"]] = {
+        "NS" => args["nsset"]
+     }
+     [true]
+   end
+
+   def do_createslavedomain(args)
+     $domain[args["domain"]] = {
+     }
+     [true]
+   end
+
+   def do_feedrecord(args)
+      args.delete "trxid"
+      rr = args["rr"]
+      name = rr["qname"]
+      qtype = rr["qtype"]
+      $domain[name] = {} unless $domain.has_key? name
+      $domain[name][qtype] = [] unless $domain[name].has_key? qtype
+      $domain[name][qtype] << rr["content"]
+      [true]
+   end
+
+   def do_replacerrset(args)
+      $domain[args["qname"]].delete args["qtype"] if $domain.has_key?(args["qname"]) and $domain[args["qname"]].has_key?(args["qtype"])
+      args["rrset"] = args["rrset"].values if args["rrset"].is_a?(Hash)
+      args["rrset"].each do |rr|
+        self.do_feedrecord({"trxid" => args["trxid"], "rr" => rr})
+      end
+      [true]
+   end 
+
+   def do_feedents(args)
+      [true]
+   end
+
+   def do_feedents3(args)
+      [true]
+   end
+
+   def do_starttransaction(args) 
+     [true]
+   end
+
+   def do_committransaction(args)
+     [true]
+   end
+
+   def do_aborttransaction(args)
+     [true]
+   end
+  
+   def do_calculatesoaserial(args)
+     return [2013060300] if args["sd"]["qname"] == "unit.test"
+     [false]
+   end
+end
+
diff --git a/modules/remotebackend/unittest_http.rb b/modules/remotebackend/unittest_http.rb
new file mode 100755 (executable)
index 0000000..25b9f36
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/ruby
+
+require 'json'
+require 'thread'
+require "rubygems"
+require "webrick"
+require "./unittest"
+
+class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
+   def initialize(server, dnsbackend)
+     @dnsbackend = dnsbackend
+     @semaphore = Mutex.new
+     @f = File.open("/tmp/tmp.txt","a")
+   end
+
+   def parse_arrays(params)
+     newparams = {}
+     params.each do |key,val|
+         if key=~/^(.*)\[(.*)\]\[(.*)\]/
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = {} unless newparams[$1].has_key? $2
+             newparams[$1][$2][$3] = val
+             params.delete key
+         elsif key=~/^(.*)\[(.*)\]/ 
+           if $2 == "" 
+             newparams[$1] = [] unless newparams.has_key? $1
+             newparams[$1] << val
+           else 
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = val
+           end
+           params.delete key
+         end
+     end
+     params.merge newparams
+   end
+
+   def parse_url(url)
+     url = url.split('/')
+     method = url.shift.downcase
+
+     # do some determining based on method names
+     args = case method
+     when "lookup"
+         {
+          "qname" => url.shift,
+          "qtype" => url.shift
+         }
+     when "list"
+        {
+          "zonename" => url.shift
+        }
+     when "getbeforeandafternamesabsolute", "getbeforeandafternames"
+        {
+           "id" => url.shift.to_i,
+           "qname" => url.shift 
+        }
+     when "getdomainmetadata", "setdomainmetadata", "getdomainkeys"
+        {
+            "name" => url.shift,
+            "kind" => url.shift
+        }
+     when "removedomainkey", "activatedomainkey", "deactivatedomainkey"
+        {
+             "id" => url.shift.to_i,
+             "name" => url.shift
+        } 
+     when "adddomainkey", "gettsigkey", "getdomaininfo"
+        {
+             "name" => url.shift
+        }
+     when "setnotified", "feedents"
+        {
+             "id" => url.shift.to_i
+        }
+     when "supermasterbackend", "createslavedomain"
+        {
+             "ip" => url.shift,
+             "domain" => url.shift
+        }
+     when "feedents3"
+        {
+             "id" => url.shift.to_i,
+             "domain" => url.shift
+        }
+     when "starttransaction"
+        {
+             "id" => url.shift.to_i,
+             "domain" => url.shift,
+            "trxid" => url.shift.to_i
+       }
+     when "committransaction", "aborttransaction"
+        {
+             "trxid" => url.shift.to_i
+        }
+     when "replacerrset"
+        {
+          "id" => url.shift.to_i,
+          "qname" => url.shift,
+          "qtype" => url.shift
+        }
+     else
+        {}
+     end
+
+     [method, args]
+   end
+
+   def do_GET(req,res)
+     req.continue
+
+     tmp = req.path[/dns\/(.*)/,1]
+     return 400, "Bad request" if (tmp.nil?)
+
+     method, args = parse_url(tmp)
+
+     method = "do_#{method}"
+    
+     # get more arguments
+     req.each do |k,v|
+        attr = k[/X-RemoteBackend-(.*)/,1]
+        if attr 
+          args[attr] = v
+        end
+     end
+
+     args = args.merge req.query
+
+     if method == "do_adddomainkey"
+        args["key"] = {
+           "flags" => args.delete("flags").to_i,
+           "active" => args.delete("active").to_i,
+           "content" => args.delete("content")
+        }
+     end
+
+     args = parse_arrays args
+
+     @f.puts method
+     @f.puts args
+
+     @semaphore.synchronize do
+       if @dnsbackend.respond_to?(method.to_sym)
+          result, log = @dnsbackend.send(method.to_sym, args)
+          body = {:result => result, :log => log}
+          res.status = 200
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = body.to_json
+        else
+          res.status = 404
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = ({:result => false, :log => ["Method not found"]}).to_json
+        end
+     end
+   end
+
+   def do_DELETE(req,res)
+     do_GET(req,res)
+   end
+   
+   def do_POST(req,res)
+     do_GET(req,res)
+   end 
+end
+
+server = WEBrick::HTTPServer.new(
+       :Port=>62434,
+       :BindAddress=>"localhost",
+#      Logger: WEBrick::Log.new("remotebackend-server.log"),
+       :AccessLog=>[ [ File.open("remotebackend-access.log", "w"), WEBrick::AccessLog::COMBINED_LOG_FORMAT ] ] 
+)
+
+be = Handler.new 
+server.mount "/dns", DNSBackendHandler, be
+
+trap('INT') { server.stop }
+trap('TERM') { server.stop }
+
+server.start
diff --git a/modules/remotebackend/unittest_pipe.rb b/modules/remotebackend/unittest_pipe.rb
new file mode 100755 (executable)
index 0000000..ea24e13
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/ruby
+
+require 'rubygems'
+require 'json'
+require './unittest'
+
+h = Handler.new()
+f = File.open "/tmp/tmp.txt","a"
+
+STDOUT.sync = true
+begin
+  STDIN.each_line do |line|
+    f.puts line
+    # expect json
+    input = {}
+    line = line.strip
+    next if line.empty?
+    begin
+      input = JSON.parse(line)
+      method = "do_#{input["method"].downcase}"
+      args = input["parameters"]
+
+      if h.respond_to?(method.to_sym) == false
+         res = false
+      elsif args.size > 0
+         res, log = h.send(method,args)
+      else
+         res, log = h.send(method)
+      end
+      puts ({:result => res, :log => log}).to_json
+      f.puts({:result => res, :log => log}).to_json
+    rescue JSON::ParserError
+      puts ({:result => false, :log => "Cannot parse input #{line}"}).to_json
+      next
+    end
+  end
+rescue SystemExit, Interrupt
+end