]> granicus.if.org Git - pdns/commitdiff
Fixes and spanking new test suite for remotebackend
authorAki Tuomi <cmouse@cmouse.fi>
Sun, 2 Jun 2013 15:46:14 +0000 (18:46 +0300)
committerAki Tuomi <cmouse@desteem.org>
Tue, 4 Jun 2013 07:33:04 +0000 (10:33 +0300)
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 8706d2a8fc7530b8fc2e910757bc23ed0879e984..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) {
@@ -59,6 +60,33 @@ void HTTPConnector::addUrlComponent(const rapidjson::Value &parameters, const ch
     }
 }
 
+template <class T> std::string buildMemberListArgs(std::string prefix, const T* value, CURL* curlContext) {
+    std::stringstream stream;
+
+    for (rapidjson::Value::ConstMemberIterator itr = value->MemberBegin(); itr != value->MemberEnd(); itr++) {
+        stream << prefix << "[" << itr->name.GetString() << "]=";
+
+        if (itr->value.IsUint64()) {
+            stream << itr->value.GetUint64();
+        } else if (itr->value.IsInt64()) {
+            stream << itr->value.GetInt64();
+        } else if (itr->value.IsUint()) {
+            stream << itr->value.GetUint();
+        } else if (itr->value.IsInt()) {
+            stream << itr->value.GetInt();
+        } else if (itr->value.IsBool()) {
+            stream << (itr->value.GetBool() ? 1 : 0);
+        } else if (itr->value.IsString()) {
+            char *tmpstr = curl_easy_escape(curlContext, itr->value.GetString(), 0);
+            stream << tmpstr;
+            curl_free(tmpstr);
+        }
+
+        stream << "&";
+    }
+
+    return stream.str();
+}
 
 // builds our request
 void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist)
@@ -66,8 +94,6 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     std::stringstream ss;
     std::string sparam;
     char *tmpstr;
-    size_t k=0;
-    k=k;
 
     // special names are qname, name, zonename, kind, others go to headers
 
@@ -79,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") { 
@@ -113,21 +136,10 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
         addUrlComponent(parameters, "ip", ss);
         addUrlComponent(parameters, "domain", ss);
         // then we need to serialize rrset payload into POST
-        for(rapidjson::Value::ConstValueIterator itr = parameters["nsset"].Begin(), k=0; itr != parameters["nsset"].End(); itr++, k++) {
-           for (rapidjson::Value::ConstMemberIterator itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); itr2++) {
-              ss2 << "nsset[" << k << "][" << itr2->name.GetString() << "]=";
-              if (itr2->value.IsUint()) {
-                 ss2 << itr2->value.GetUint();
-              } else if (itr2->value.IsInt()) {
-                 ss2 << itr2->value.GetInt();
-              } else if (itr2->value.IsBool()) {
-                 ss2 << (itr2->value.GetBool() ? 1 : 0);
-              } else if (itr2->value.IsString()) {
-                 tmpstr = curl_easy_escape(d_c, itr2->value.GetString(), 0);
-                 ss2 << tmpstr;
-                 curl_free(tmpstr);
-              }
-           }
+        size_t index = 0;
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nsset"].Begin(); itr != parameters["nsset"].End(); itr++) {
+            index++;
+            ss2 << buildMemberListArgs("nsset[" + boost::lexical_cast<std::string>(index) + "]", itr, d_c);
         }
         // then give it to curl
         std::string out = ss2.str();
@@ -136,50 +148,66 @@ 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;
-        for(rapidjson::Value::ConstValueIterator itr = parameters["nsset"].Begin(), k=0; itr != parameters["nsset"].End(); itr++, k++) {
-           for (rapidjson::Value::ConstMemberIterator itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); itr2++) {
-              ss2 << "rrset[" << k << "][" << itr2->name.GetString() << "]=";
-              if (itr2->value.IsUint()) {
-                 ss2 << itr2->value.GetUint();
-              } else if (itr2->value.IsInt()) {
-                 ss2 << itr2->value.GetInt();
-              } else if (itr2->value.IsBool()) {
-                 ss2 << (itr2->value.GetBool() ? 1 : 0);
-              } else if (itr2->value.IsString()) {
-                 tmpstr = curl_easy_escape(d_c, itr2->value.GetString(), 0);
-                 ss2 << tmpstr;
-                 curl_free(tmpstr);
-              }
-           }
+        size_t index = 0;
+        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);
         }
         // then give it to curl
         std::string out = ss2.str();
         curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
         curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
     } else if (method == "feedRecord") {
+        std::string out = buildMemberListArgs("rr", &parameters["rr"], d_c);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "feedEnts") {
         std::stringstream ss2;
-        for (rapidjson::Value::ConstMemberIterator itr2 = parameters["rr"].MemberBegin(); itr2 != parameters["rr"].MemberEnd(); itr2++) {
-           ss2 << "rr[" << itr2->name.GetString() << "]=";
-           if (itr2->value.IsUint()) {
-              ss2 << itr2->value.GetUint();
-           } else if (itr2->value.IsInt()) {
-              ss2 << itr2->value.GetInt();
-           } else if (itr2->value.IsBool()) {
-              ss2 << (itr2->value.GetBool() ? 1 : 0);
-           } else if (itr2->value.IsString()) {
-              tmpstr = curl_easy_escape(d_c, itr2->value.GetString(), 0);
-              ss2 << tmpstr;
-              curl_free(tmpstr);
-           }
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nonterm"].Begin(); itr != parameters["nonterm"].End(); itr++) {
+          tmpstr = curl_easy_escape(d_c, itr->GetString(), 0);
+          ss2 << "nonterm[]=" << tmpstr << "&";
+          curl_free(tmpstr);
         }
         std::string out = ss2.str();
         curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
         curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "feedEnts3") {
+        std::stringstream ss2;
+        addUrlComponent(parameters, "domain", ss);
+        ss2 << "times=" << parameters["times"].GetInt() << "&salt=" << parameters["salt"].GetString() << "&narrow=" << (parameters["narrow"].GetBool() ? 1 : 0) << "&";
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nonterm"].Begin(); itr != parameters["nonterm"].End(); itr++) {
+          tmpstr = curl_easy_escape(d_c, itr->GetString(), 0);
+          ss2 << "nonterm[]=" << tmpstr << "&";
+          curl_free(tmpstr);
+        }
+        std::string out = ss2.str();
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } 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"];
@@ -187,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();
@@ -196,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);
@@ -210,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