summaryrefslogtreecommitdiffstats
path: root/test-dnsdistkvs_cc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test-dnsdistkvs_cc.cc')
-rw-r--r--test-dnsdistkvs_cc.cc450
1 files changed, 450 insertions, 0 deletions
diff --git a/test-dnsdistkvs_cc.cc b/test-dnsdistkvs_cc.cc
new file mode 100644
index 0000000..8c5d756
--- /dev/null
+++ b/test-dnsdistkvs_cc.cc
@@ -0,0 +1,450 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-kvs.hh"
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+static const ComboAddress v4ToMask("203.0.113.255");
+static const ComboAddress v6ToMask("2001:db8:ff:ff:ff:ff:ff:ff");
+
+static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress& lc, const ComboAddress& rem, const DNSQuestion& dq, const DNSName& plaintextDomain)
+{
+ /* source IP */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySourceIP>(32, 128, false);
+ std::string value;
+ /* local address is not in the db, remote is */
+ BOOST_CHECK_EQUAL(kvs->getValue(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr)), value), false);
+ BOOST_CHECK_EQUAL(kvs->keyExists(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr))), false);
+ BOOST_CHECK(kvs->keyExists(std::string(reinterpret_cast<const char*>(&dq.ids.origRemote.sin4.sin_addr.s_addr), sizeof(dq.ids.origRemote.sin4.sin_addr.s_addr))));
+
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the remote addr");
+ }
+ }
+
+ /* masked source IP */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySourceIP>(25, 65, false);
+
+ auto keys = lookupKey->getKeys(v4ToMask);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the masked v4 addr");
+ }
+
+ keys = lookupKey->getKeys(v6ToMask);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the masked v6 addr");
+ }
+ }
+
+ /* source IP + port */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySourceIP>(32, 128, true);
+ std::string value;
+ BOOST_CHECK(kvs->keyExists(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&rem.sin4.sin_port), sizeof(rem.sin4.sin_port))));
+
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the remote addr + port");
+ }
+ }
+
+ const DNSName subdomain = DNSName("sub") + dq.ids.qname;
+ const DNSName notPDNS("not-powerdns.com.");
+
+ /* qname match, in wire format */
+ {
+ std::string value;
+ auto lookupKey = make_unique<KeyValueLookupKeyQName>(true);
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+ }
+
+ /* other domain, should not match */
+ keys = lookupKey->getKeys(notPDNS);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* subdomain, should not match */
+ keys = lookupKey->getKeys(subdomain);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* this domain was inserted in plaintext, the wire format lookup should not match */
+ keys = lookupKey->getKeys(plaintextDomain);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+ }
+
+ /* qname match, in plain text */
+ {
+ std::string value;
+ auto lookupKey = make_unique<KeyValueLookupKeyQName>(false);
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* other domain, should not match */
+ keys = lookupKey->getKeys(notPDNS);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* subdomain, should not match */
+ keys = lookupKey->getKeys(subdomain);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* this domain was inserted in plaintext, so it should match */
+ keys = lookupKey->getKeys(plaintextDomain);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the plaintext domain");
+ }
+ }
+
+ /* suffix match in wire format */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, true);
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), dq.ids.qname.countLabels());
+ BOOST_REQUIRE(!keys.empty());
+ BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toDNSStringLC());
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), false);
+
+ /* other domain, should not match */
+ keys = lookupKey->getKeys(notPDNS);
+ BOOST_CHECK_EQUAL(keys.size(), notPDNS.countLabels());
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* subdomain, the second key should match */
+ keys = lookupKey->getKeys(subdomain);
+ BOOST_REQUIRE_EQUAL(keys.size(), subdomain.countLabels());
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+
+ /* this domain was inserted in plaintext, the wire format lookup should not match */
+ keys = lookupKey->getKeys(plaintextDomain);
+ BOOST_CHECK_EQUAL(keys.size(), plaintextDomain.countLabels());
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+ }
+
+ /* suffix match in plain text */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, false);
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), dq.ids.qname.countLabels());
+ BOOST_REQUIRE(!keys.empty());
+ BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toStringRootDot());
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), false);
+
+ /* other domain, should not match */
+ keys = lookupKey->getKeys(notPDNS);
+ BOOST_CHECK_EQUAL(keys.size(), notPDNS.countLabels());
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* subdomain, should not match in plain text */
+ keys = lookupKey->getKeys(subdomain);
+ BOOST_REQUIRE_EQUAL(keys.size(), subdomain.countLabels());
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+ }
+
+ /* this domain was inserted in plaintext, it should match */
+ keys = lookupKey->getKeys(plaintextDomain);
+ BOOST_REQUIRE_EQUAL(keys.size(), plaintextDomain.countLabels());
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the plaintext domain");
+ }
+
+ /* suffix match in wire format, we require at least 2 labels */
+ {
+ auto lookupKey = make_unique<KeyValueLookupKeySuffix>(2, true);
+ auto keys = lookupKey->getKeys(dq);
+ BOOST_CHECK_EQUAL(keys.size(), 1U);
+ BOOST_REQUIRE(!keys.empty());
+ BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toDNSStringLC());
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+ value.clear();
+
+ /* subdomain */
+ keys = lookupKey->getKeys(subdomain);
+ BOOST_REQUIRE_EQUAL(keys.size(), 2U);
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
+ BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+ }
+}
+
+#if defined(HAVE_LMDB)
+static void doKVSRangeChecks(std::unique_ptr<KeyValueStore>& kvs)
+{
+ {
+ /* do a range-based lookup */
+ const ComboAddress first("2001:0db8:0000:0000:0000:0000:0000:0000");
+ const ComboAddress inside("2001:0db8:7fff:ffff:ffff:ffff:ffff:ffff");
+ const ComboAddress last("2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff");
+ const ComboAddress notInRange1("2001:0db7:ffff:ffff:ffff:ffff:ffff:ffff");
+ const ComboAddress notInRange2("2001:0db9:0000:0000:0000:0000:0000:0000");
+ const std::string expectedValue = std::string(reinterpret_cast<const char*>(&first.sin6.sin6_addr.s6_addr), sizeof(first.sin6.sin6_addr.s6_addr)) + std::string("any other data");
+
+ auto check = [expectedValue, &kvs](const ComboAddress& key, bool shouldBeFound) {
+ // cerr<<"Checking "<<key.toString()<<", should "<<(shouldBeFound ? "" : "NOT ")<<"be found"<<endl;
+ auto lookupKey = std::string(reinterpret_cast<const char*>(&key.sin6.sin6_addr.s6_addr), sizeof(key.sin6.sin6_addr.s6_addr));
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getRangeValue(lookupKey, value), shouldBeFound);
+ if (shouldBeFound) {
+ BOOST_CHECK_EQUAL(value, expectedValue);
+ }
+ };
+
+ check(first, true);
+ check(last, true);
+ check(inside, true);
+ check(notInRange1, false);
+ check(notInRange2, false);
+ }
+
+ {
+ const ComboAddress first("192.0.2.1:0");
+ const ComboAddress inside("192.0.2.1:42");
+ const ComboAddress last("192.0.2.1:16383");
+ const ComboAddress notInRange1("192.0.2.0:65535");
+ const ComboAddress notInRange2("192.0.2.1:16384");
+ const std::string expectedValue = std::string(reinterpret_cast<const char*>(&first.sin4.sin_addr.s_addr), sizeof(first.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&first.sin4.sin_port), sizeof(first.sin4.sin_port)) + std::string("any other data");
+
+ auto check = [expectedValue, &kvs](const ComboAddress& key, bool shouldBeFound) {
+ // cerr<<"Checking "<<key.toStringWithPort()<<", should "<<(shouldBeFound ? "" : "NOT ")<<"be found"<<endl;
+ auto lookupKey = std::string(reinterpret_cast<const char*>(&key.sin4.sin_addr.s_addr), sizeof(key.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&key.sin4.sin_port), sizeof(key.sin4.sin_port));
+ std::string value;
+ BOOST_CHECK_EQUAL(kvs->getRangeValue(lookupKey, value), shouldBeFound);
+ if (shouldBeFound) {
+ BOOST_CHECK_EQUAL(value, expectedValue);
+ }
+ };
+
+ check(first, true);
+ check(last, true);
+ check(inside, true);
+ check(notInRange1, false);
+ check(notInRange2, false);
+ }
+
+}
+#endif // defined(HAVE_LMDB)
+
+#endif // defined(HAVE_LMDB) || defined(HAVE_CDB)
+
+BOOST_AUTO_TEST_SUITE(dnsdistkvs_cc)
+
+#ifdef HAVE_LMDB
+BOOST_AUTO_TEST_CASE(test_LMDB) {
+
+ InternalQueryState ids;
+ ids.qname = DNSName("powerdns.com.");
+ DNSName plaintextDomain("powerdns.org.");
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.origDest = ComboAddress("192.0.2.1:53");
+ ids.origRemote = ComboAddress("192.0.2.128:42");
+ PacketBuffer packet(sizeof(dnsheader));
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.queryRealTime.start();
+ struct timespec expiredTime;
+ /* the internal QPS limiter does not use the real time */
+ gettime(&expiredTime);
+
+ DNSQuestion dq(ids, packet);
+ ComboAddress v4Masked(v4ToMask);
+ ComboAddress v6Masked(v6ToMask);
+ v4Masked.truncate(25);
+ v6Masked.truncate(65);
+
+ const ComboAddress firstRangeAddr6("2001:0db8:0000:0000:0000:0000:0000:0000");
+ const ComboAddress lastRangeAddr6("2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff");
+ const ComboAddress firstRangeAddr4("192.0.2.1:0");
+ const ComboAddress lastRangeAddr4("192.0.2.1:16383");
+
+ string dbPath("/tmp/test_lmdb.XXXXXX");
+ {
+ MDBEnv env(dbPath.c_str(), MDB_NOSUBDIR, 0600, 50);
+ auto transaction = env.getRWTransaction();
+ auto dbi = transaction->openDB("db-name", MDB_CREATE);
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the remote addr"));
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_port), sizeof(ids.origRemote.sin4.sin_port))), MDBInVal("this is the value for the remote addr + port"));
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the masked v4 addr"));
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr))), MDBInVal("this is the value for the masked v6 addr"));
+ transaction->put(dbi, MDBInVal(dq.ids.qname.toDNSStringLC()), MDBInVal("this is the value for the qname"));
+ transaction->put(dbi, MDBInVal(plaintextDomain.toStringRootDot()), MDBInVal("this is the value for the plaintext domain"));
+
+ transaction->commit();
+ }
+
+ {
+ MDBEnv env(dbPath.c_str(), MDB_NOSUBDIR, 0600, 50);
+ auto transaction = env.getRWTransaction();
+ auto dbi = transaction->openDB("range-db-name", MDB_CREATE);
+ /* range-based lookups */
+ std::string value = std::string(reinterpret_cast<const char*>(&firstRangeAddr6.sin6.sin6_addr.s6_addr), sizeof(firstRangeAddr6.sin6.sin6_addr.s6_addr)) + std::string("any other data");
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&lastRangeAddr6.sin6.sin6_addr.s6_addr), sizeof(lastRangeAddr6.sin6.sin6_addr.s6_addr))), MDBInVal(value));
+
+ value = std::string(reinterpret_cast<const char*>(&firstRangeAddr4.sin4.sin_addr.s_addr), sizeof(firstRangeAddr4.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&firstRangeAddr4.sin4.sin_port), sizeof(firstRangeAddr4.sin4.sin_port)) + std::string("any other data");
+ transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&lastRangeAddr4.sin4.sin_addr.s_addr), sizeof(lastRangeAddr4.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&lastRangeAddr4.sin4.sin_port), sizeof(lastRangeAddr4.sin4.sin_port))), MDBInVal(value));
+
+ transaction->commit();
+ }
+
+ std::unique_ptr<KeyValueStore> lmdb = std::make_unique<LMDBKVStore>(dbPath, "db-name");
+ doKVSChecks(lmdb, ids.origDest, ids.origRemote, dq, plaintextDomain);
+ lmdb.reset();
+
+ lmdb = std::make_unique<LMDBKVStore>(dbPath, "range-db-name");
+ doKVSRangeChecks(lmdb);
+
+ unlink(dbPath.c_str());
+ dbPath += "-lock";
+ unlink(dbPath.c_str());
+
+ /*
+ std::string value;
+ DTime dt;
+ dt.set();
+ for (size_t idx = 0; idx < 10000000; idx++) {
+ auto keys = lookupKey->getKeys(dq);
+ for (const auto& key : keys) {
+ value.clear();
+ BOOST_CHECK_EQUAL(lmdb->getValue(key, value), true);
+ BOOST_CHECK_EQUAL(value, "this is the value of the tag");
+ }
+ }
+ cerr<<dt.udiff()/1000/1000<<endl;
+ */
+}
+#endif /* HAVE_LMDB */
+
+#ifdef HAVE_CDB
+BOOST_AUTO_TEST_CASE(test_CDB) {
+
+ InternalQueryState ids;
+ ids.qname = DNSName("powerdns.com.");
+ DNSName plaintextDomain("powerdns.org.");
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.origDest = ComboAddress("192.0.2.1:53");
+ ids.origRemote = ComboAddress("192.0.2.128:42");
+ PacketBuffer packet(sizeof(dnsheader));
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.queryRealTime.start();
+ struct timespec expiredTime;
+ /* the internal QPS limiter does not use the real time */
+ gettime(&expiredTime);
+
+ DNSQuestion dq(ids, packet);
+ ComboAddress v4Masked(v4ToMask);
+ ComboAddress v6Masked(v6ToMask);
+ v4Masked.truncate(25);
+ v6Masked.truncate(65);
+
+ char db[] = "/tmp/test_cdb.XXXXXX";
+ {
+ int fd = mkstemp(db);
+ BOOST_REQUIRE(fd >= 0);
+ CDBWriter writer(fd);
+ BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)), "this is the value for the remote addr"));
+ BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_port), sizeof(ids.origRemote.sin4.sin_port)), "this is the value for the remote addr + port"));
+ BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr)), "this is the value for the masked v4 addr"));
+ BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr)), "this is the value for the masked v6 addr"));
+ BOOST_REQUIRE(writer.addEntry(dq.ids.qname.toDNSStringLC(), "this is the value for the qname"));
+ BOOST_REQUIRE(writer.addEntry(plaintextDomain.toStringRootDot(), "this is the value for the plaintext domain"));
+ writer.close();
+ }
+
+ std::unique_ptr<KeyValueStore> cdb = std::make_unique<CDBKVStore>(db, 0);
+ doKVSChecks(cdb, ids.origDest, ids.origRemote, dq, plaintextDomain);
+
+ unlink(db);
+
+ /*
+ std::string value;
+ DTime dt;
+ dt.set();
+ auto lookupKey = make_unique<KeyValueLookupKeySourceIP>();
+ for (size_t idx = 0; idx < 100000000; idx++) {
+ auto keys = lookupKey->getKeys(dq);
+ for (const auto& key : keys) {
+ if (!cdb->getValue(key, value)) {
+ cerr<<"key not found"<<endl;
+ break;
+ }
+ if (value != "this is the value for the remote addr") {
+ cerr<<"invalid value found"<<endl;
+ break;
+ }
+ }
+ }
+ cerr<<dt.udiff()/1000/1000<<endl;
+ */
+}
+#endif /* HAVE_CDB */
+
+BOOST_AUTO_TEST_SUITE_END()