Parcourir la source

Merge #2378

The Zone loader class (glue class between zone updater and MasterLoader).
Michal 'vorner' Vaner il y a 12 ans
Parent
commit
91d77e0da5

+ 1 - 0
src/lib/datasrc/Makefile.am

@@ -38,6 +38,7 @@ libb10_datasrc_la_SOURCES += client_list.h client_list.cc
 libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libb10_datasrc_la_SOURCES += master_loader_callbacks.h
 libb10_datasrc_la_SOURCES += master_loader_callbacks.h
 libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
 libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
+libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
 nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 
 

+ 49 - 6
src/lib/datasrc/memory/memory_client.cc

@@ -209,14 +209,20 @@ private:
     RdataIteratorPtr rdata_iterator_;
     RdataIteratorPtr rdata_iterator_;
     bool separate_rrs_;
     bool separate_rrs_;
     bool ready_;
     bool ready_;
+    bool examined_rrsigs_;
+    // In case there's nsec3 namespace in the zone, it is represented the same
+    // way as the usual namespace. So we reuse the iterator implementation for
+    // it.
+    ZoneIteratorPtr nsec3_namespace_;
 public:
 public:
     MemoryIterator(const RRClass& rrclass,
     MemoryIterator(const RRClass& rrclass,
-                   const ZoneTree& tree, const Name& origin,
-                   bool separate_rrs) :
+                   const ZoneTree& tree, const NSEC3Data* nsec3_data,
+                   const Name& origin, bool separate_rrs) :
         rrclass_(rrclass),
         rrclass_(rrclass),
         tree_(tree),
         tree_(tree),
         separate_rrs_(separate_rrs),
         separate_rrs_(separate_rrs),
-        ready_(true)
+        ready_(true),
+        examined_rrsigs_(false)
     {
     {
         // Find the first node (origin) and preserve the node chain for future
         // Find the first node (origin) and preserve the node chain for future
         // searches
         // searches
@@ -235,10 +241,25 @@ public:
                 rdata_iterator_ = rrset_->getRdataIterator();
                 rdata_iterator_ = rrset_->getRdataIterator();
             }
             }
         }
         }
+
+        // If we have the NSEC3 namespace, get an iterator for it so we can
+        // delegate to it later.
+        if (nsec3_data != NULL) {
+            nsec3_namespace_ =
+                ZoneIteratorPtr(new MemoryIterator(rrclass,
+                                                   nsec3_data->getNSEC3Tree(),
+                                                   NULL, origin,
+                                                   separate_rrs));
+        }
     }
     }
 
 
     virtual ConstRRsetPtr getNextRRset() {
     virtual ConstRRsetPtr getNextRRset() {
         if (!ready_) {
         if (!ready_) {
+            // We are done iterating. But in case there's the nsec3 one,
+            // iterate through that one.
+            if (nsec3_namespace_ != ZoneIteratorPtr()) {
+                return (nsec3_namespace_->getNextRRset());
+            }
             isc_throw(Unexpected, "Iterating past the zone end");
             isc_throw(Unexpected, "Iterating past the zone end");
         }
         }
         /*
         /*
@@ -259,13 +280,19 @@ public:
                     rrset_.reset(new TreeNodeRRset(rrclass_,
                     rrset_.reset(new TreeNodeRRset(rrclass_,
                                                    node_, set_node_, true));
                                                    node_, set_node_, true));
                     rdata_iterator_ = rrset_->getRdataIterator();
                     rdata_iterator_ = rrset_->getRdataIterator();
+                    examined_rrsigs_ = false;
                 }
                 }
             }
             }
         }
         }
         if (node_ == NULL) {
         if (node_ == NULL) {
             // That's all, folks
             // That's all, folks
             ready_ = false;
             ready_ = false;
-            return (ConstRRsetPtr());
+            if (nsec3_namespace_ != ZoneIteratorPtr()) {
+                // In case we have the NSEC3 namespace, get one from there.
+                return (nsec3_namespace_->getNextRRset());
+            } else {
+                return (ConstRRsetPtr());
+            }
         }
         }
 
 
         if (separate_rrs_) {
         if (separate_rrs_) {
@@ -273,10 +300,24 @@ public:
             // 'current' rdata
             // 'current' rdata
             RRsetPtr result(new RRset(rrset_->getName(),
             RRsetPtr result(new RRset(rrset_->getName(),
                                       rrset_->getClass(),
                                       rrset_->getClass(),
-                                      rrset_->getType(),
+                                      // If we are looking into the signature,
+                                      // we need to adjust the type too.
+                                      examined_rrsigs_ ? RRType::RRSIG() :
+                                          rrset_->getType(),
                                       rrset_->getTTL()));
                                       rrset_->getTTL()));
             result->addRdata(rdata_iterator_->getCurrent());
             result->addRdata(rdata_iterator_->getCurrent());
             rdata_iterator_->next();
             rdata_iterator_->next();
+            if (!examined_rrsigs_ && rdata_iterator_->isLast()) {
+                // We got to the last RR of the RRset, but we need to look at
+                // the signatures too, if there are any.
+                examined_rrsigs_ = true;
+                const ConstRRsetPtr rrsig = rrset_->getRRsig();
+                if (rrsig != ConstRRsetPtr()) {
+                    rrset_ = rrsig;
+                    rdata_iterator_ = rrsig->getRdataIterator();
+                } // else - no RRSIG. rdata_iterator_ stays at last, next
+                  // condition applies
+            }
             if (rdata_iterator_->isLast()) {
             if (rdata_iterator_->isLast()) {
                 // all used up, next.
                 // all used up, next.
                 set_node_ = set_node_->getNext();
                 set_node_ = set_node_->getNext();
@@ -286,6 +327,7 @@ public:
                     rrset_.reset(new TreeNodeRRset(rrclass_,
                     rrset_.reset(new TreeNodeRRset(rrclass_,
                                                    node_, set_node_, true));
                                                    node_, set_node_, true));
                     rdata_iterator_ = rrset_->getRdataIterator();
                     rdata_iterator_ = rrset_->getRdataIterator();
+                    examined_rrsigs_ = false;
                 }
                 }
             }
             }
             return (result);
             return (result);
@@ -317,7 +359,8 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
 
 
     return (ZoneIteratorPtr(new MemoryIterator(
     return (ZoneIteratorPtr(new MemoryIterator(
                                 getClass(),
                                 getClass(),
-                                result.zone_data->getZoneTree(), name,
+                                result.zone_data->getZoneTree(),
+                                result.zone_data->getNSEC3Data(), name,
                                 separate_rrs)));
                                 separate_rrs)));
 }
 }
 
 

+ 1 - 0
src/lib/datasrc/tests/Makefile.am

@@ -60,6 +60,7 @@ run_unittests_SOURCES += zone_finder_context_unittest.cc
 run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 run_unittests_SOURCES += client_list_unittest.cc
 run_unittests_SOURCES += client_list_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += zone_loader_unittest.cc
 
 
 # We need the actual module implementation in the tests (they are not part
 # We need the actual module implementation in the tests (they are not part
 # of libdatasrc)
 # of libdatasrc)

+ 19 - 0
src/lib/datasrc/tests/memory/memory_client_unittest.cc

@@ -690,6 +690,25 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
     EXPECT_EQ(ConstRRsetPtr(), iterator2->getNextRRset());
     EXPECT_EQ(ConstRRsetPtr(), iterator2->getNextRRset());
 }
 }
 
 
+// Test we get RRSIGs and NSEC3s too for iterating with separate RRs
+TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
+    client_->load(Name("example.org"),
+                       TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org"), true));
+    bool seen_rrsig = false, seen_nsec3 = false;
+    for (ConstRRsetPtr rrset = iterator->getNextRRset();
+         rrset != ConstRRsetPtr(); rrset = iterator->getNextRRset()) {
+        if (rrset->getType() == RRType::RRSIG()) {
+            seen_rrsig = true;
+        } else if (rrset->getType() == RRType::NSEC3()) {
+            seen_nsec3 = true;
+        }
+    }
+
+    EXPECT_TRUE(seen_rrsig);
+    EXPECT_TRUE(seen_nsec3);
+}
+
 TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
 TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
     client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
     client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
     ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
     ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));

+ 397 - 0
src/lib/datasrc/tests/zone_loader_unittest.cc

@@ -0,0 +1,397 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/zone_loader.h>
+#include <datasrc/data_source.h>
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/memory_client.h>
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+using isc::dns::RRClass;
+using isc::dns::Name;
+using isc::dns::RRType;
+using isc::dns::ConstRRsetPtr;
+using std::string;
+using std::vector;
+using boost::shared_ptr;
+using namespace isc::datasrc;
+
+namespace {
+
+class MockClient : public DataSourceClient {
+public:
+    MockClient() :
+        commit_called_(false),
+        missing_zone_(false),
+        rrclass_(RRClass::IN())
+    {}
+    virtual FindResult findZone(const Name&) const {
+        isc_throw(isc::NotImplemented, "Method not used in tests");
+    };
+    virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+        getJournalReader(const Name&, uint32_t, uint32_t) const
+    {
+        isc_throw(isc::NotImplemented, "Method not used in tests");
+    }
+    virtual ZoneUpdaterPtr getUpdater(const Name& name, bool replace,
+                                      bool journaling) const;
+    // We store some information about what was happening here.
+    // It is publicly accessible, since this is private testing class
+    // anyway, so no need to dress it fancy into getters. Some are mutable,
+    // since many client methods are const, but we still want to know they
+    // were called.
+    mutable vector<Name> provided_updaters_;
+    // We store string representations of the RRsets. This is simpler than
+    // copying them and we can't really put them into shared pointers, because
+    // we get them as references.
+    vector<string> rrsets_;
+    bool commit_called_;
+    // If set to true, getUpdater returns NULL
+    bool missing_zone_;
+    // The pretended class of the client. Usualy IN, but can be overriden.
+    RRClass rrclass_;
+};
+
+// The updater isn't really correct according to the API. For example,
+// the whole client can be committed only once in its lifetime. The
+// updaters would influence each other if there were more. But we
+// don't need more updaters in the same test, so it doesn't matter
+// and this way, it is much simpler.
+class Updater : public ZoneUpdater {
+public:
+    Updater(MockClient* client) :
+        client_(client),
+        finder_(client_->rrclass_)
+    {}
+    virtual ZoneFinder& getFinder() {
+        return (finder_);
+    }
+    virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
+        if (client_->commit_called_) {
+            isc_throw(DataSourceError, "Add after commit");
+        }
+        client_->rrsets_.push_back(rrset.toText());
+    }
+    virtual void deleteRRset(const isc::dns::AbstractRRset&) {
+        isc_throw(isc::NotImplemented, "Method not used in tests");
+    }
+    virtual void commit() {
+        client_->commit_called_ = true;
+    }
+private:
+    MockClient* client_;
+    class Finder : public ZoneFinder {
+    public:
+        Finder(const RRClass& rrclass) :
+            class_(rrclass)
+        {}
+        virtual RRClass getClass() const {
+            return (class_);
+        }
+        virtual Name getOrigin() const {
+            isc_throw(isc::NotImplemented, "Method not used in tests");
+        }
+        virtual shared_ptr<Context> find(const Name&, const RRType&,
+                                         const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Method not used in tests");
+        }
+        virtual shared_ptr<Context> findAll(const Name&,
+                                            vector<ConstRRsetPtr>&,
+                                            const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Method not used in tests");
+        }
+        virtual FindNSEC3Result findNSEC3(const Name&, bool) {
+            isc_throw(isc::NotImplemented, "Method not used in tests");
+        }
+    private:
+        const RRClass class_;
+    } finder_;
+};
+
+ZoneUpdaterPtr
+MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
+    if (missing_zone_) {
+        return (ZoneUpdaterPtr());
+    }
+    EXPECT_TRUE(replace);
+    EXPECT_FALSE(journaling);
+    provided_updaters_.push_back(name);
+    // const_cast is bad. But the const on getUpdater seems wrong in the first
+    // place, since updater will be modifying the data there. And the updater
+    // wants to store data into the client so we can examine it later.
+    return (ZoneUpdaterPtr(new Updater(const_cast<MockClient*>(this))));
+}
+
+class ZoneLoaderTest : public ::testing::Test {
+protected:
+    ZoneLoaderTest() :
+        rrclass_(RRClass::IN()),
+        ztable_segment_(memory::ZoneTableSegment::
+                        create(isc::data::NullElement(), rrclass_)),
+        source_client_(ztable_segment_, rrclass_)
+    {}
+    void prepareSource(const Name& zone, const char* filename) {
+        // TODO:
+        // Currently, load uses an urelated implementation. In the long term,
+        // the method will probably be deprecated. At that time, we should
+        // probably prepare the data in some other way (using sqlite3 or
+        // something). This is simpler for now.
+        source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
+    }
+private:
+    const RRClass rrclass_;
+    // This is because of the in-memory client. We use it to read data
+    // from. It is still easier than setting up sqlite3 client, since
+    // we have this one in the linked library.
+
+    // FIXME: We should be destroying it by ZoneTableSegment::destroy.
+    // But the shared pointer won't let us, will it?
+    shared_ptr<memory::ZoneTableSegment> ztable_segment_;
+protected:
+    memory::InMemoryClient source_client_;
+    // This one is mocked. It will help us see what is happening inside.
+    // Also, mocking it is simpler than setting up an sqlite3 client.
+    MockClient destination_client_;
+};
+
+// Use the loader to load an unsigned zone.
+TEST_F(ZoneLoaderTest, copyUnsigned) {
+    prepareSource(Name::ROOT_NAME(), "root.zone");
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+    // It gets the updater directly in the constructor
+    ASSERT_EQ(1, destination_client_.provided_updaters_.size());
+    EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+    // Now load the whole zone
+    loader.load();
+    EXPECT_TRUE(destination_client_.commit_called_);
+    // We don't check the whole zone. We check the first and last and the
+    // count, which should be enough.
+
+    // The count is 34 because we expect the RRs to be separated.
+    EXPECT_EQ(34, destination_client_.rrsets_.size());
+    // Ensure known order.
+    std::sort(destination_client_.rrsets_.begin(),
+              destination_client_.rrsets_.end());
+    EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+              destination_client_.rrsets_.front());
+    EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+              destination_client_.rrsets_.back());
+
+    // It isn't possible to try again now
+    EXPECT_THROW(loader.load(), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+    // Even 0, which should load nothing, returns the error
+    EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Try loading incrementally.
+TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
+    prepareSource(Name::ROOT_NAME(), "root.zone");
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+
+    // Try loading few RRs first.
+    loader.loadIncremental(10);
+    // We should get the 10 we asked for
+    EXPECT_EQ(10, destination_client_.rrsets_.size());
+    // Not committed yet, we didn't complete the loading
+    EXPECT_FALSE(destination_client_.commit_called_);
+
+    // This is unusual, but allowed. Check it doesn't do anything
+    loader.loadIncremental(0);
+    EXPECT_EQ(10, destination_client_.rrsets_.size());
+    EXPECT_FALSE(destination_client_.commit_called_);
+
+    // We can finish the rest
+    loader.loadIncremental(30);
+    EXPECT_EQ(34, destination_client_.rrsets_.size());
+    EXPECT_TRUE(destination_client_.commit_called_);
+
+    // No more loading now
+    EXPECT_THROW(loader.load(), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Check we can load RRSIGs and NSEC3 (which could break due to them being
+// in separate namespace)
+TEST_F(ZoneLoaderTest, copySigned) {
+    prepareSource(Name("example.org"), "example.org.nsec3-signed");
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      source_client_);
+    loader.load();
+
+    // All the RRs are there, including the ones in NSEC3 namespace
+    EXPECT_EQ(14, destination_client_.rrsets_.size());
+    EXPECT_TRUE(destination_client_.commit_called_);
+    // Same trick with sorting to know where they are
+    std::sort(destination_client_.rrsets_.begin(),
+              destination_client_.rrsets_.end());
+    // Due to the R at the beginning, this one should be last
+    EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
+              "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
+              destination_client_.rrsets_[0]);
+    EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
+              "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
+              " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
+              "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
+              "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
+              destination_client_.rrsets_[1]);
+}
+
+// If the destination zone does not exist, it throws
+TEST_F(ZoneLoaderTest, copyMissingDestination) {
+    destination_client_.missing_zone_ = true;
+    prepareSource(Name::ROOT_NAME(), "root.zone");
+    EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+                            source_client_), DataSourceError);
+}
+
+// If the source zone does not exist, it throws
+TEST_F(ZoneLoaderTest, copyMissingSource) {
+    EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+                            source_client_), DataSourceError);
+}
+
+// The class of the source and destination are different
+TEST_F(ZoneLoaderTest, classMismatch) {
+    destination_client_.rrclass_ = RRClass::CH();
+    prepareSource(Name::ROOT_NAME(), "root.zone");
+    EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+                            source_client_), isc::InvalidParameter);
+}
+
+// Load an unsigned zone, all at once
+TEST_F(ZoneLoaderTest, loadUnsigned) {
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+                      TEST_DATA_DIR "/root.zone");
+    // It gets the updater directly in the constructor
+    ASSERT_EQ(1, destination_client_.provided_updaters_.size());
+    EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+    // Now load the whole zone
+    loader.load();
+    EXPECT_TRUE(destination_client_.commit_called_);
+    // We don't check the whole zone. We check the first and last and the
+    // count, which should be enough.
+
+    // The count is 34 because we expect the RRs to be separated.
+    EXPECT_EQ(34, destination_client_.rrsets_.size());
+    // Ensure known order.
+    std::sort(destination_client_.rrsets_.begin(),
+              destination_client_.rrsets_.end());
+    EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+              destination_client_.rrsets_.front());
+    EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+              destination_client_.rrsets_.back());
+
+    // It isn't possible to try again now
+    EXPECT_THROW(loader.load(), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+    // Even 0, which should load nothing, returns the error
+    EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Try loading from master file incrementally.
+TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+                      TEST_DATA_DIR "/root.zone");
+
+    // Try loading few RRs first.
+    loader.loadIncremental(10);
+    // We should get the 10 we asked for
+    EXPECT_EQ(10, destination_client_.rrsets_.size());
+    // Not committed yet, we didn't complete the loading
+    EXPECT_FALSE(destination_client_.commit_called_);
+
+    // This is unusual, but allowed. Check it doesn't do anything
+    loader.loadIncremental(0);
+    EXPECT_EQ(10, destination_client_.rrsets_.size());
+    EXPECT_FALSE(destination_client_.commit_called_);
+
+    // We can finish the rest
+    loader.loadIncremental(30);
+    EXPECT_EQ(34, destination_client_.rrsets_.size());
+    EXPECT_TRUE(destination_client_.commit_called_);
+
+    // No more loading now
+    EXPECT_THROW(loader.load(), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+    EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// If the destination zone does not exist, it throws
+TEST_F(ZoneLoaderTest, loadMissingDestination) {
+    destination_client_.missing_zone_ = true;
+    EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+                            TEST_DATA_DIR "/root.zone"), DataSourceError);
+}
+
+// Check we can load RRSIGs and NSEC3 (which could break due to them being
+// in separate namespace)
+TEST_F(ZoneLoaderTest, loadSigned) {
+    ZoneLoader loader(destination_client_, Name("example.org"),
+                      TEST_DATA_DIR "/example.org.nsec3-signed");
+    loader.load();
+
+    // All the RRs are there, including the ones in NSEC3 namespace
+    EXPECT_EQ(14, destination_client_.rrsets_.size());
+    EXPECT_TRUE(destination_client_.commit_called_);
+    // Same trick with sorting to know where they are
+    std::sort(destination_client_.rrsets_.begin(),
+              destination_client_.rrsets_.end());
+    // Due to the R at the beginning, this one should be last
+    EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
+              "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
+              destination_client_.rrsets_[0]);
+    EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
+              "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
+              " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
+              "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
+              "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
+              destination_client_.rrsets_[1]);
+}
+
+// Test it throws when there's no such file
+TEST_F(ZoneLoaderTest, loadNoSuchFile) {
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+                      "This file does not exist");
+    EXPECT_THROW(loader.load(), MasterFileError);
+    EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// And it also throws when there's a syntax error in the master file
+TEST_F(ZoneLoaderTest, loadSyntaxError) {
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+                      // This is not a master file for sure
+                      // (misusing a file that happens to be there
+                      // already).
+                      TEST_DATA_DIR "/example.org.sqlite3");
+    EXPECT_THROW(loader.load(), MasterFileError);
+    EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+}

+ 128 - 0
src/lib/datasrc/zone_loader.cc

@@ -0,0 +1,128 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/zone_loader.h>
+#include <datasrc/master_loader_callbacks.h>
+
+#include <datasrc/client.h>
+#include <datasrc/data_source.h>
+#include <datasrc/iterator.h>
+#include <datasrc/zone.h>
+
+#include <dns/rrset.h>
+
+using isc::dns::Name;
+using isc::dns::ConstRRsetPtr;
+using isc::dns::MasterLoader;
+
+namespace isc {
+namespace datasrc {
+
+ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
+                       DataSourceClient& source) :
+    // Separate the RRsets as that is possibly faster (the data source doesn't
+    // have to aggregate them) and also because our limit semantics.
+    iterator_(source.getIterator(zone_name, true)),
+    updater_(destination.getUpdater(zone_name, true, false)),
+    complete_(false)
+{
+    // The getIterator should never return NULL. So we check it.
+    // Or should we throw instead?
+    assert(iterator_ != ZoneIteratorPtr());
+    // In case the zone doesn't exist in the destination, throw
+    if (updater_ == ZoneUpdaterPtr()) {
+        isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
+                  "destination data source, can't fill it with data");
+    }
+    // The dereference of zone_finder is safe, if we can get iterator, we can
+    // get a finder.
+    //
+    // TODO: We probably need a getClass on the data source itself.
+    if (source.findZone(zone_name).zone_finder->getClass() !=
+        updater_->getFinder().getClass()) {
+        isc_throw(isc::InvalidParameter,
+                  "Source and destination class mismatch");
+    }
+}
+
+ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
+                       const char* filename) :
+    updater_(destination.getUpdater(zone_name, true, false)),
+    complete_(false),
+    loaded_ok_(true)
+{
+    if (updater_ == ZoneUpdaterPtr()) {
+        isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
+                  "destination data source, can't fill it with data");
+    } else {
+        loader_.reset(new
+                      MasterLoader(filename, zone_name,
+                                   // TODO: Maybe we should have getClass()
+                                   // on the data source?
+                                   updater_->getFinder().getClass(),
+                                   createMasterLoaderCallbacks(zone_name,
+                                       updater_->getFinder().getClass(),
+                                       &loaded_ok_),
+                                   createMasterLoaderAddCallback(*updater_)));
+    }
+}
+
+namespace {
+
+// Copy up to limit RRsets from source to destination
+bool
+copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
+           size_t limit)
+{
+    size_t loaded = 0;
+    while (loaded < limit) {
+        const ConstRRsetPtr rrset(source->getNextRRset());
+        if (rrset == ConstRRsetPtr()) {
+            // Done loading, no more RRsets in the input.
+            return (true);
+        } else {
+            destination->addRRset(*rrset);
+        }
+        ++loaded;
+    }
+    return (false); // Not yet, there may be more
+}
+
+} // end unnamed namespace
+
+bool
+ZoneLoader::loadIncremental(size_t limit) {
+    if (complete_) {
+        isc_throw(isc::InvalidOperation,
+                  "Loading has been completed previously");
+    }
+
+    if (iterator_ == ZoneIteratorPtr()) {
+        assert(loader_.get() != NULL);
+        complete_ = loader_->loadIncremental(limit);
+        if (complete_ && !loaded_ok_) {
+            isc_throw(MasterFileError, "Error while loading master file");
+        }
+    } else {
+        complete_ = copyRRsets(updater_, iterator_, limit);
+    }
+
+    if (complete_) {
+        updater_->commit();
+    }
+    return (complete_);
+}
+
+} // end namespace datasrc
+} // end namespace isc

+ 158 - 0
src/lib/datasrc/zone_loader.h

@@ -0,0 +1,158 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_ZONE_LOADER_H
+#define DATASRC_ZONE_LOADER_H
+
+#include <datasrc/data_source.h>
+
+#include <dns/master_loader.h>
+
+#include <cstdlib> // For size_t
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dns {
+// Forward declaration
+class Name;
+}
+namespace datasrc {
+
+// Forward declarations
+class DataSourceClient;
+class ZoneIterator;
+typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
+class ZoneUpdater;
+typedef boost::shared_ptr<ZoneUpdater> ZoneUpdaterPtr;
+
+/// \brief Exception thrown when there's a problem with master file.
+///
+/// This is thrown by the ZoneLoader when there's a fatal problem with
+/// a master file being loaded.
+class MasterFileError : public DataSourceError {
+public:
+    MasterFileError(const char* file, size_t line, const char* what) :
+        DataSourceError(file, line, what)
+    {}
+};
+
+/// \brief Class to load data into a data source client.
+///
+/// This is a small wrapper class that is able to load data into a data source.
+/// It can load either from another data source or from a master file. The
+/// purpose of the class is only to hold the state for incremental loading.
+///
+/// The old content of zone is discarded and no journal is stored.
+class ZoneLoader {
+public:
+    /// \brief Constructor from master file.
+    ///
+    /// This initializes the zone loader to load from a master file.
+    ///
+    /// \param destination The data source into which the loaded data should
+    ///     go.
+    /// \param zone_name The origin of the zone. The class is implicit in the
+    ///     destination.
+    /// \param master_file Path to the master file to read data from.
+    /// \throw DataSourceError in case the zone does not exist in destination.
+    ///     This class does not support creating brand new zones, only loading
+    ///     data into them. In case a new zone is needed, it must be created
+    ///     beforehand.
+    /// \throw DataSourceError in case of other (possibly low-level) errors,
+    ///     such as read-only data source or database error.
+    ZoneLoader(DataSourceClient& destination, const isc::dns::Name& zone_name,
+               const char* master_file);
+
+    /// \brief Constructor from another data source.
+    ///
+    /// This initializes the zone loader to read from another data source.
+    /// It'll effectively copy data from one data source to another.
+    ///
+    /// \param destination The data source into which the loaded data should
+    ///     go.
+    /// \param zone_name The origin of the zone.
+    /// \param source The data source from which the data would be read.
+    /// \throw InvalidParameter in case the class of destination and source
+    ///     differs.
+    /// \throw NotImplemented in case the source data source client doesn't
+    ///     provide an iterator.
+    /// \throw DataSourceError in case the zone does not exist in destination.
+    ///     This class does not support creating brand new zones, only loading
+    ///     data into them. In case a new zone is needed, it must be created
+    ///     beforehand.
+    /// \throw DataSourceError in case the zone does not exist in the source.
+    /// \throw DataSourceError in case of other (possibly low-level) errors,
+    ///     such as read-only data source or database error.
+    ZoneLoader(DataSourceClient& destination, const isc::dns::Name& zone_name,
+               DataSourceClient& source);
+
+    /// \brief Perform the whole load.
+    ///
+    /// This performs the whole loading operation. It may take a long time.
+    ///
+    /// \throw InvalidOperation in case the loading was already completed
+    ///     before this call.
+    /// \throw DataSourceError in case some error (possibly low-level) happens.
+    /// \throw MasterFileError when the master_file is badly formatted or some
+    ///     similar problem is found when loading the master file.
+    void load() {
+        while (!loadIncremental(1000)) { // 1000 is arbitrary largish number
+            // Body intentionally left blank.
+        }
+    }
+
+    /// \brief Load up to limit RRs.
+    ///
+    /// This performs a part of the loading. In case there's enough data in the
+    /// source, it copies limit RRs. It can copy less RRs during the final call
+    /// (when there's less than limit left).
+    ///
+    /// This can be called repeatedly until the whole zone is loaded, having
+    /// pauses in the loading for some purposes (for example reporting
+    /// progress).
+    ///
+    /// \param limit The maximum allowed number of RRs to be loaded during this
+    ///     call.
+    /// \return True in case the loading is completed, false if there's more
+    ///     to load.
+    /// \throw InvalidOperation in case the loading was already completed
+    ///     before this call (by load() or by a loadIncremental that returned
+    ///     true).
+    /// \throw DataSourceError in case some error (possibly low-level) happens.
+    /// \throw MasterFileError when the master_file is badly formatted or some
+    ///     similar problem is found when loading the master file.
+    /// \note If the limit is exactly the number of RRs available to be loaded,
+    ///     the method still returns false and true'll be returned on the next
+    ///     call (which will load 0 RRs). This is because the end of iterator or
+    ///     master file is detected when reading past the end, not when the last
+    ///     one is read.
+    bool loadIncremental(size_t limit);
+private:
+    /// \brief The iterator used as source of data in case of the copy mode.
+    const ZoneIteratorPtr iterator_;
+    /// \brief The destination zone updater
+    const ZoneUpdaterPtr updater_;
+    /// \brief The master loader (for the master file mode)
+    boost::scoped_ptr<isc::dns::MasterLoader> loader_;
+    /// \brief Indicator if loading was completed
+    bool complete_;
+    /// \brief Was the loading successful?
+    bool loaded_ok_;
+};
+
+}
+}
+
+#endif