Parcourir la source

Merge #2207

"define and implement (datasrc::memory::)ZoneUpdater class"

It is now ZoneWriter.

No changelog.

Conflicts:
	src/lib/datasrc/memory/Makefile.am
Michal 'vorner' Vaner il y a 12 ans
Parent
commit
7868cb5a0c

+ 3 - 0
src/lib/datasrc/memory/Makefile.am

@@ -24,6 +24,9 @@ libdatasrc_memory_la_SOURCES += zone_table_segment_local.h zone_table_segment_lo
 libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc
 libdatasrc_memory_la_SOURCES += zone_data_loader.h zone_data_loader.cc
 libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
+libdatasrc_memory_la_SOURCES += zone_writer.h
+libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
+libdatasrc_memory_la_SOURCES += load_action.h
 
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 

+ 47 - 0
src/lib/datasrc/memory/load_action.h

@@ -0,0 +1,47 @@
+// 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 LOAD_ACTION_H
+#define LOAD_ACTION_H
+
+#include <boost/function.hpp>
+
+namespace isc {
+// Forward declarations
+namespace util{
+class MemorySegment;
+}
+namespace datasrc {
+namespace memory {
+class ZoneData;
+
+/// \brief Callback to load data into the memory
+///
+/// This is called from the ZoneWriter whenever there's need to load the
+/// zone data. The callback should allocate new ZoneData and fill it with
+/// the zone content. It is up to the callback to know where or how to
+/// load the data, or even the origin and class of the zone (it is assumed
+/// the callback will be some kind of functor).
+///
+/// All data should be allocated from the passed MemorySegment. The ownership
+/// is passed onto the caller.
+///
+/// It must not return NULL.
+typedef boost::function<ZoneData*(util::MemorySegment&)> LoadAction;
+
+}
+}
+}
+
+#endif

+ 42 - 0
src/lib/datasrc/memory/zone_table_segment.h

@@ -16,6 +16,7 @@
 #define __ZONE_TABLE_SEGMENT_H__
 
 #include <datasrc/memory/zone_table.h>
+#include "load_action.h"
 #include <cc/data.h>
 #include <util/memory_segment.h>
 
@@ -24,8 +25,14 @@
 #include <stdlib.h>
 
 namespace isc {
+// Some forward declarations
+namespace dns {
+class Name;
+class RRClass;
+}
 namespace datasrc {
 namespace memory {
+class ZoneWriter;
 
 /// \brief Memory-management independent entry point that contains a
 /// pointer to a zone table in memory.
@@ -45,6 +52,26 @@ public:
         return (table.get());
     }
 
+    /// \brief Method to set the internal table
+    ///
+    /// The interface is tentative, we don't know if this is the correct place
+    /// and way to set the data. But for now, we need something to be there
+    /// at least for the tests. So we have this. For this reason, there are
+    /// no tests for this method directly. Do not use in actual
+    /// implementation.
+    ///
+    /// It can be used only once, to initially set it. It can't replace the
+    /// one already there.
+    ///
+    /// \param table Pointer to the table to use.
+    /// \throw isc::Unexpected if called the second time.
+    void setTable(ZoneTable* table) {
+        if (this->table.get() != NULL) {
+            isc_throw(isc::Unexpected, "Replacing table");
+        }
+        this->table = table;
+    }
+
 private:
     boost::interprocess::offset_ptr<ZoneTable> table;
 };
@@ -101,6 +128,21 @@ public:
     ///
     /// \param segment The segment to destroy.
     static void destroy(ZoneTableSegment* segment);
+
+    /// \brief Create a zone write corresponding to this segment
+    ///
+    /// This creates a new write that can be used to update zones
+    /// inside this zone table segment.
+    ///
+    /// \param loadAction Callback to provide the actual data.
+    /// \param origin The origin of the zone to reload.
+    /// \param rrclass The class of the zone to reload.
+    /// \return New instance of a zone writer. The ownership is passed
+    ///     onto the caller and the caller needs to \c delete it when
+    ///     it's done with the writer.
+    virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
+                                      const dns::Name& origin,
+                                      const dns::RRClass& rrclass) = 0;
 };
 
 } // namespace memory

+ 9 - 0
src/lib/datasrc/memory/zone_table_segment_local.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <datasrc/memory/zone_table_segment_local.h>
+#include "zone_writer_local.h"
 
 using namespace isc::util;
 
@@ -38,6 +39,14 @@ ZoneTableSegmentLocal::getMemorySegment() {
      return (mem_sgmt_);
 }
 
+ZoneWriter*
+ZoneTableSegmentLocal::getZoneWriter(const LoadAction& load_action,
+                                     const dns::Name& name,
+                                     const dns::RRClass& rrclass)
+{
+    return (new ZoneWriterLocal(this, load_action, name, rrclass));
+}
+
 } // namespace memory
 } // namespace datasrc
 } // namespace isc

+ 4 - 0
src/lib/datasrc/memory/zone_table_segment_local.h

@@ -54,6 +54,10 @@ public:
     /// implementation (a MemorySegmentLocal instance).
     virtual isc::util::MemorySegment& getMemorySegment();
 
+    /// \brief Concrete implementation of ZoneTableSegment::getZoneWriter
+    virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
+                                      const dns::Name& origin,
+                                      const dns::RRClass& rrclass);
 private:
     ZoneTableHeader header_;
     isc::util::MemorySegmentLocal mem_sgmt_;

+ 89 - 0
src/lib/datasrc/memory/zone_writer.h

@@ -0,0 +1,89 @@
+// 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 MEM_ZONE_WRITER_H
+#define MEM_ZONE_WRITER_H
+
+#include "load_action.h"
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief Does an update to a zone.
+///
+/// This abstract base class represents the work of a reload of a zone.
+/// The work is divided into three stages -- load(), install() and cleanup().
+/// They should be called in this order for the effect to take place.
+///
+/// We divide them so the update of zone data can be done asynchronously,
+/// in a different thread. The install() operation is the only one that needs
+/// to be done in a critical section.
+///
+/// Each derived class implementation must provide the strong exception
+/// guarantee for each public method. That is, when any of the methods
+/// throws, the entire state should stay the same as before the call
+/// (how to achieve that may be implementation dependant).
+class ZoneWriter {
+public:
+    /// \brief Get the zone data into memory.
+    ///
+    /// This is the part that does the time-consuming loading into the memory.
+    /// This can be run in a separate thread, for example. It has no effect on
+    /// the data actually served, it only prepares them for future use.
+    ///
+    /// This is the first method you should call on the object. Never call it
+    /// multiple times.
+    ///
+    /// \note As this contains reading of files or other data sources, or with
+    ///     some other source of the data to load, it may throw quite anything.
+    ///     If it throws, do not call any other methods on the object and
+    ///     discard it.
+    /// \note After successful load(), you have to call cleanup() some time
+    ///     later.
+    /// \throw isc::InvalidOperation if called second time.
+    virtual void load() = 0;
+
+    /// \brief Put the changes to effect.
+    ///
+    /// This replaces the old version of zone with the one previously prepared
+    /// by load(). It takes ownership of the old zone data, if any.
+    ///
+    /// You may call it only after successful load() and at most once.
+    ///
+    /// The operation is expected to be fast and is meant to be used inside
+    /// a critical section.
+    ///
+    /// This may throw in rare cases, depending on the concrete implementation.
+    /// If it throws, you still need to call cleanup().
+    ///
+    /// \throw isc::InvalidOperation if called without previous load() or for
+    ///     the second time or cleanup() was called already.
+    virtual void install() = 0;
+
+    /// \brief Clean up resources.
+    ///
+    /// This releases all resources held by owned zone data. That means the
+    /// one loaded by load() in case install() was not called or was not
+    /// successful, or the one replaced in install().
+    ///
+    /// Generally, this should never throw.
+    virtual void cleanup() = 0;
+};
+
+}
+}
+}
+
+#endif

+ 93 - 0
src/lib/datasrc/memory/zone_writer_local.cc

@@ -0,0 +1,93 @@
+// 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 "zone_writer_local.h"
+#include "zone_data.h"
+#include "zone_table_segment_local.h"
+
+#include <memory>
+
+using std::auto_ptr;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+ZoneWriterLocal::ZoneWriterLocal(ZoneTableSegmentLocal* segment,
+                                 const LoadAction& load_action,
+                                 const dns::Name& origin,
+                                 const dns::RRClass& rrclass) :
+    segment_(segment),
+    load_action_(load_action),
+    origin_(origin),
+    rrclass_(rrclass),
+    zone_data_(NULL),
+    state_(ZW_UNUSED)
+{}
+
+ZoneWriterLocal::~ZoneWriterLocal() {
+    // Clean up everything there might be left if someone forgot, just
+    // in case.
+    cleanup();
+}
+
+void
+ZoneWriterLocal::load() {
+    if (state_ != ZW_UNUSED) {
+        isc_throw(isc::InvalidOperation, "Trying to load twice");
+    }
+
+    zone_data_ = load_action_(segment_->getMemorySegment());
+
+    if (zone_data_ == NULL) {
+        // Bug inside load_action_.
+        isc_throw(isc::InvalidOperation, "No data returned from load action");
+    }
+
+    state_ = ZW_LOADED;
+}
+
+void
+ZoneWriterLocal::install() {
+    if (state_ != ZW_LOADED) {
+        isc_throw(isc::InvalidOperation, "No data to install");
+    }
+
+
+    ZoneTable* table(segment_->getHeader().getTable());
+    if (table == NULL) {
+        isc_throw(isc::Unexpected, "No zone table present");
+    }
+    const ZoneTable::AddResult result(table->addZone(
+                                          segment_->getMemorySegment(),
+                                          rrclass_, origin_, zone_data_));
+
+    state_ = ZW_INSTALLED;
+    zone_data_ = result.zone_data;
+}
+
+void
+ZoneWriterLocal::cleanup() {
+    // We eat the data (if any) now.
+
+    if (zone_data_ != NULL) {
+        ZoneData::destroy(segment_->getMemorySegment(), zone_data_, rrclass_);
+        zone_data_ = NULL;
+        state_ = ZW_CLEANED;
+    }
+}
+
+}
+}
+}

+ 95 - 0
src/lib/datasrc/memory/zone_writer_local.h

@@ -0,0 +1,95 @@
+// 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 MEM_ZONE_WRITER_LOCAL_H
+#define MEM_ZONE_WRITER_LOCAL_H
+
+#include "zone_writer.h"
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+class ZoneData;
+class ZoneTableSegmentLocal;
+
+/// \brief Writer implementation which loads data locally.
+///
+/// This implementation prepares a clean zone data and lets one callback
+/// to fill it and another to install it somewhere. The class does mostly
+/// nothing (and delegates the work to the callbacks), just stores little bit
+/// of state between the calls.
+class ZoneWriterLocal : public ZoneWriter {
+public:
+    /// \brief Constructor
+    ///
+    /// \param segment The zone table segment to store the zone into.
+    /// \param load_action The callback used to load data.
+    /// \param install_action The callback used to install the loaded zone.
+    /// \param rrclass The class of the zone.
+    ZoneWriterLocal(ZoneTableSegmentLocal* segment,
+                    const LoadAction& load_action, const dns::Name& name,
+                    const dns::RRClass& rrclass);
+
+    /// \brief Destructor
+    ~ZoneWriterLocal();
+
+    /// \brief Loads the data.
+    ///
+    /// This calls the load_action (passed to constructor) and stores the
+    /// data for future use.
+    ///
+    /// \throw isc::InvalidOperation if it is called the second time in
+    ///     lifetime of the object.
+    /// \throw Whatever the load_action throws, it is propagated up.
+    virtual void load();
+
+    /// \brief Installs the zone.
+    ///
+    /// It modifies the zone table accessible through the segment (passed to
+    /// constructor).
+    ///
+    /// \throw isc::InvalidOperation if it is called the second time in
+    ///     lifetime of the object or if load() was not called previously or if
+    ///     cleanup() was already called.
+    virtual void install();
+
+    /// \brief Clean up memory.
+    ///
+    /// Cleans up the memory used by load()ed zone if not yet installed, or
+    /// the old zone replaced by install().
+    virtual void cleanup();
+private:
+    ZoneTableSegmentLocal* segment_;
+    LoadAction load_action_;
+    dns::Name origin_;
+    dns::RRClass rrclass_;
+    ZoneData* zone_data_;
+    enum State {
+        ZW_UNUSED,
+        ZW_LOADED,
+        ZW_INSTALLED,
+        ZW_CLEANED
+    };
+    State state_;
+};
+
+}
+}
+}
+
+#endif

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

@@ -33,6 +33,7 @@ run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
 run_unittests_SOURCES += zone_table_segment_unittest.cc
+run_unittests_SOURCES += zone_writer_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 24 - 0
src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc

@@ -13,8 +13,14 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer_local.h>
 #include <gtest/gtest.h>
 
+#include <boost/scoped_ptr.hpp>
+
+using boost::scoped_ptr;
+using isc::dns::Name;
+using isc::dns::RRClass;
 using namespace isc::datasrc::memory;
 using namespace isc::data;
 using namespace isc::util;
@@ -80,4 +86,22 @@ TEST_F(ZoneTableSegmentTest, getMemorySegment) {
     EXPECT_TRUE(mem_sgmt.allMemoryDeallocated());
 }
 
+ZoneData*
+loadAction(MemorySegment&) {
+    // The function won't be called, so this is OK
+    return (NULL);
+}
+
+// Test we can get a writer.
+TEST_F(ZoneTableSegmentTest, getZoneWriter) {
+    scoped_ptr<ZoneWriter>
+        writer(segment_->getZoneWriter(loadAction, Name("example.org"),
+                                       RRClass::IN()));
+    // We have to get something
+    EXPECT_NE(static_cast<void*>(NULL), writer.get());
+    // And for now, it should be the local writer
+    EXPECT_NE(static_cast<void*>(NULL),
+              dynamic_cast<ZoneWriterLocal*>(writer.get()));
+}
+
 } // anonymous namespace

+ 249 - 0
src/lib/datasrc/tests/memory/zone_writer_unittest.cc

@@ -0,0 +1,249 @@
+// 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/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_table_segment_local.h>
+#include <datasrc/memory/zone_data.h>
+
+#include <cc/data.h>
+#include <dns/rrclass.h>
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
+using boost::scoped_ptr;
+using boost::bind;
+using isc::dns::RRClass;
+using isc::dns::Name;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class TestException {};
+
+class ZoneWriterLocalTest : public ::testing::Test {
+public:
+    ZoneWriterLocalTest() :
+        // FIXME: The NullElement probably isn't the best one, but we don't
+        // know how the config will look, so it just fills the argument
+        // (which is currently ignored)
+        segment_(ZoneTableSegment::create(isc::data::NullElement())),
+        writer_(new
+            ZoneWriterLocal(dynamic_cast<ZoneTableSegmentLocal*>(segment_.
+                                                                 get()),
+                            bind(&ZoneWriterLocalTest::loadAction, this, _1),
+                            Name("example.org"), RRClass::IN())),
+        load_called_(false),
+        load_throw_(false),
+        load_null_(false),
+        load_data_(false)
+    {
+        // TODO: The setTable is only a temporary interface
+        segment_->getHeader().
+            setTable(ZoneTable::create(segment_->getMemorySegment(),
+                                       RRClass::IN()));
+    }
+    void TearDown() {
+        // Release the writer
+        writer_.reset();
+        // Release the table we used
+        ZoneTable::destroy(segment_->getMemorySegment(),
+                           segment_->getHeader().getTable(), RRClass::IN());
+        // And check we freed all memory
+        EXPECT_TRUE(segment_->getMemorySegment().allMemoryDeallocated());
+    }
+protected:
+    scoped_ptr<ZoneTableSegment> segment_;
+    scoped_ptr<ZoneWriterLocal> writer_;
+    bool load_called_;
+    bool load_throw_;
+    bool load_null_;
+    bool load_data_;
+private:
+    ZoneData* loadAction(isc::util::MemorySegment& segment) {
+        // Make sure it is the correct segment passed. We know the
+        // exact instance, can compare pointers to them.
+        EXPECT_EQ(&segment_->getMemorySegment(), &segment);
+        // We got called
+        load_called_ = true;
+        if (load_throw_) {
+            throw TestException();
+        }
+
+        if (load_null_) {
+            // Be nasty to the caller and return NULL, which is forbidden
+            return (NULL);
+        }
+        ZoneData* data = ZoneData::create(segment, Name("example.org"));
+        if (load_data_) {
+            // Put something inside. The node itself should be enough for
+            // the tests.
+            ZoneNode* node(NULL);
+            data->insertName(segment, Name("subdomain.example.org"), &node);
+            EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+        }
+        return (data);
+    }
+};
+
+// We call it the way we are supposed to, check every callback is called in the
+// right moment.
+TEST_F(ZoneWriterLocalTest, correctCall) {
+    // Nothing called before we call it
+    EXPECT_FALSE(load_called_);
+
+    // Just the load gets called now
+    EXPECT_NO_THROW(writer_->load());
+    EXPECT_TRUE(load_called_);
+    load_called_ = false;
+
+    EXPECT_NO_THROW(writer_->install());
+    EXPECT_FALSE(load_called_);
+
+    // We don't check explicitly how this works, but call it to free memory. If
+    // everything is freed should be checked inside the TearDown.
+    EXPECT_NO_THROW(writer_->cleanup());
+}
+
+TEST_F(ZoneWriterLocalTest, loadTwice) {
+    // Load it the first time
+    EXPECT_NO_THROW(writer_->load());
+    EXPECT_TRUE(load_called_);
+    load_called_ = false;
+
+    // The second time, it should not be possible
+    EXPECT_THROW(writer_->load(), isc::InvalidOperation);
+    EXPECT_FALSE(load_called_);
+
+    // The object should not be damaged, try installing and clearing now
+    EXPECT_NO_THROW(writer_->install());
+    EXPECT_FALSE(load_called_);
+
+    // We don't check explicitly how this works, but call it to free memory. If
+    // everything is freed should be checked inside the TearDown.
+    EXPECT_NO_THROW(writer_->cleanup());
+}
+
+// Try loading after call to install and call to cleanup. Both is
+// forbidden.
+TEST_F(ZoneWriterLocalTest, loadLater) {
+    // Load first, so we can install
+    EXPECT_NO_THROW(writer_->load());
+    EXPECT_NO_THROW(writer_->install());
+    // Reset so we see nothing is called now
+    load_called_ = false;
+
+    EXPECT_THROW(writer_->load(), isc::InvalidOperation);
+    EXPECT_FALSE(load_called_);
+
+    // Cleanup and try loading again. Still shouldn't work.
+    EXPECT_NO_THROW(writer_->cleanup());
+
+    EXPECT_THROW(writer_->load(), isc::InvalidOperation);
+    EXPECT_FALSE(load_called_);
+}
+
+// Try calling install at various bad times
+TEST_F(ZoneWriterLocalTest, invalidInstall) {
+    // Nothing loaded yet
+    EXPECT_THROW(writer_->install(), isc::InvalidOperation);
+    EXPECT_FALSE(load_called_);
+
+    EXPECT_NO_THROW(writer_->load());
+    load_called_ = false;
+    // This install is OK
+    EXPECT_NO_THROW(writer_->install());
+    // But we can't call it second time now
+    EXPECT_THROW(writer_->install(), isc::InvalidOperation);
+    EXPECT_FALSE(load_called_);
+}
+
+// We check we can clean without installing first and nothing bad
+// happens. We also misuse the testcase to check we can't install
+// after cleanup.
+TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
+    EXPECT_NO_THROW(writer_->load());
+    EXPECT_NO_THROW(writer_->cleanup());
+
+    EXPECT_TRUE(load_called_);
+
+    // We cleaned up, no way to install now
+    EXPECT_THROW(writer_->install(), isc::InvalidOperation);
+}
+
+// Test the case when load callback throws
+TEST_F(ZoneWriterLocalTest, loadThrows) {
+    load_throw_ = true;
+    EXPECT_THROW(writer_->load(), TestException);
+
+    // We can't install now
+    EXPECT_THROW(writer_->install(), isc::InvalidOperation);
+    EXPECT_TRUE(load_called_);
+
+    // But we can cleanup
+    EXPECT_NO_THROW(writer_->cleanup());
+}
+
+// Check the strong exception guarantee - if it throws, nothing happened
+// to the content.
+TEST_F(ZoneWriterLocalTest, retry) {
+    // First attempt fails due to some exception.
+    load_throw_ = true;
+    EXPECT_THROW(writer_->load(), TestException);
+    // This one shall succeed.
+    load_called_ = load_throw_ = false;
+    // We want some data inside.
+    load_data_ = true;
+    EXPECT_NO_THROW(writer_->load());
+    // And this one will fail again. But the old data will survive.
+    load_data_ = false;
+    EXPECT_THROW(writer_->load(), isc::InvalidOperation);
+
+    // The rest still works correctly
+    EXPECT_NO_THROW(writer_->install());
+    ZoneTable* const table(segment_->getHeader().getTable());
+    const ZoneTable::FindResult found(table->findZone(Name("example.org")));
+    ASSERT_EQ(isc::datasrc::result::SUCCESS, found.code);
+    // For some reason it doesn't seem to work by the ZoneNode typedef, using
+    // the full definition instead. At least on some compilers.
+    const isc::datasrc::memory::DomainTreeNode<RdataSet>* node;
+    EXPECT_EQ(isc::datasrc::memory::DomainTree<RdataSet>::EXACTMATCH,
+              found.zone_data->getZoneTree().
+              find(Name("subdomain.example.org"), &node));
+    EXPECT_NO_THROW(writer_->cleanup());
+}
+
+// Check the writer defends itsefl when load action returns NULL
+TEST_F(ZoneWriterLocalTest, loadNull) {
+    load_null_ = true;
+    EXPECT_THROW(writer_->load(), isc::InvalidOperation);
+
+    // We can't install that
+    EXPECT_THROW(writer_->install(), isc::InvalidOperation);
+
+    // It should be possible to clean up safely
+    EXPECT_NO_THROW(writer_->cleanup());
+}
+
+// Check the object cleans up in case we forget it.
+TEST_F(ZoneWriterLocalTest, autoCleanUp) {
+    // Load data and forget about it. It should get released
+    // when the writer itself is destroyed.
+    EXPECT_NO_THROW(writer_->load());
+}
+
+}