Browse Source

Merged trac #347: Implement query counters in auth module (branches/trac347 r3685:r4016)


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@4026 e5f2f494-b856-4b98-b285-d166d9295462
Yoshitaka Aharen 14 years ago
parent
commit
064529e8c0

+ 11 - 0
ChangeLog

@@ -1,3 +1,14 @@
+  140.  [func]		y-aharen
+	src/bin/auth: Added a feature to count queries and send counter
+	values to statistics periodically. To support it, added wrapping
+	class of asio::deadline_timer to use as interval timer.
+	The counters can be seen using the "Stats show" command from
+	bindctl.  The result would look like:
+	  ... "auth.queries.tcp": 1, "auth.queries.udp": 1 ...
+	Using the "Auth sendstats" command you can make b10-auth send the
+	counters to b10-stats immediately.
+	(Trac #347, svn r4026)
+
   139.  [build]		jreed
 	Introduced configure option and make targets for generating
 	Python code coverage report. This adds new make targets:

+ 1 - 1
configure.ac

@@ -371,7 +371,7 @@ if test "${boost_include_path}" ; then
 	BOOST_INCLUDES="-I${boost_include_path}"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp],,
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
 CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(BOOST_INCLUDES)

+ 1 - 0
src/bin/auth/Makefile.am

@@ -41,6 +41,7 @@ b10_auth_SOURCES += auth_srv.cc auth_srv.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += config.cc config.h
 b10_auth_SOURCES += common.h
+b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la

+ 5 - 0
src/bin/auth/auth.spec.pre.in

@@ -59,6 +59,11 @@
         "command_name": "shutdown",
         "command_description": "Shut down authoritative DNS server",
         "command_args": []
+      },
+      {
+        "command_name": "sendstats",
+        "command_description": "Send data to a statistics module at once",
+        "command_args": []
       }
     ]
   }

+ 41 - 0
src/bin/auth/auth_srv.cc

@@ -55,6 +55,7 @@
 #include <auth/config.h>
 #include <auth/auth_srv.h>
 #include <auth/query.h>
+#include <auth/statistics.h>
 
 using namespace std;
 
@@ -100,6 +101,9 @@ public:
 
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
+
+    /// Query counters for statistics
+    AuthCounters counters_;
 private:
     std::string db_file_;
 
@@ -111,6 +115,9 @@ private:
 
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
+
+    /// Increment query counter
+    void incCounter(const int protocol);
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -118,6 +125,7 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     config_session_(NULL), verbose_mode_(false),
     xfrin_session_(NULL),
     memory_datasrc_class_(RRClass::IN()),
+    counters_(verbose_mode_),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
 {
@@ -294,6 +302,11 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
     impl_->config_session_ = config_session;
 }
 
+void
+AuthSrv::setStatisticsSession(AbstractSession* statistics_session) {
+    impl_->counters_.setStatisticsSession(statistics_session);
+}
+
 ModuleCCSession*
 AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
@@ -429,6 +442,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
     message->setHeaderFlag(Message::HEADERFLAG_AA);
     message->setRcode(Rcode::NOERROR());
 
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (remote_edns) {
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         local_edns->setDNSSECAwareness(dnssec_ok);
@@ -476,6 +492,9 @@ bool
 AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
                               OutputBufferPtr buffer)
 {
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         if (verbose_mode_) {
             cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
@@ -601,6 +620,19 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message,
     return (true);
 }
 
+void
+AuthSrvImpl::incCounter(const int protocol) {
+    // Increment query counter.
+    if (protocol == IPPROTO_UDP) {
+        counters_.inc(AuthCounters::COUNTER_UDP_QUERY);
+    } else if (protocol == IPPROTO_TCP) {
+        counters_.inc(AuthCounters::COUNTER_TCP_QUERY);
+    } else {
+        // unknown protocol
+        isc_throw(Unexpected, "Unknown protocol: " << protocol);
+    }
+}
+
 ConstElementPtr
 AuthSrvImpl::setDbFile(ConstElementPtr config) {
     ConstElementPtr answer = isc::config::createAnswer();
@@ -670,3 +702,12 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
         return (isc::config::createAnswer(1, error.what()));
     }
 }
+
+bool AuthSrv::submitStatistics() const {
+    return (impl_->counters_.submitStatistics());
+}
+
+uint64_t
+AuthSrv::getCounter(const AuthCounters::CounterType type) const {
+    return (impl_->counters_.getCounter(type));
+}

+ 47 - 0
src/bin/auth/auth_srv.h

@@ -27,6 +27,7 @@
 #include <config/ccsession.h>
 
 #include <asiolink/asiolink.h>
+#include <auth/statistics.h>
 
 namespace isc {
 namespace datasrc {
@@ -62,6 +63,7 @@ class AuthSrvImpl;
 ///
 /// The design of this class is still in flux.  It's quite likely to change
 /// in future versions.
+///
 class AuthSrv {
     ///
     /// \name Constructors, Assignment Operator and Destructor.
@@ -96,6 +98,8 @@ public:
     /// \param message Pointer to the \c Message object
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param server Pointer to the \c DNSServer
+    ///
+    /// \throw isc::Unexpected Protocol type of \a message is unexpected
     void processMessage(const asiolink::IOMessage& io_message,
                         isc::dns::MessagePtr message,
                         isc::dns::OutputBufferPtr buffer,
@@ -281,6 +285,49 @@ public:
     void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
                           MemoryDataSrcPtr memory_datasrc);
 
+    /// \brief Set the communication session with Statistics.
+    ///
+    /// This function never throws an exception as far as
+    /// AuthCounters::setStatisticsSession() doesn't throw.
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and
+    /// session frameworks, at which point the session will probably
+    /// be passed on construction of the server.
+    ///
+    /// \param statistics_session A Session object over which statistics
+    /// information is exchanged with statistics module.
+    /// The session must be established before setting in the server
+    /// object.
+    /// Ownership isn't transferred: the caller is responsible for keeping
+    /// this object to be valid while the server object is working and for
+    /// disconnecting the session and destroying the object when the server
+    /// is shutdown.
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This function can throw an exception from
+    /// AuthCounters::submitStatistics().
+    ///
+    /// \return true on success, false on failure (e.g. session timeout,
+    /// session error).
+    bool submitStatistics() const;
+
+    /// \brief Get the value of counter in the AuthCounters.
+    /// 
+    /// This function calls AuthCounters::getCounter() and
+    /// returns its return value.
+    ///
+    /// This function never throws an exception as far as
+    /// AuthCounters::getCounter() doesn't throw.
+    /// 
+    /// Note: Currently this function is for testing purpose only.
+    ///
+    /// \param type Type of a counter to get the value of
+    ///
+    /// \return the value of the counter.
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+
 private:
     AuthSrvImpl* impl_;
     asiolink::IOService* io_service_;

+ 1 - 0
src/bin/auth/benchmarks/Makefile.am

@@ -11,6 +11,7 @@ query_bench_SOURCES = query_bench.cc
 query_bench_SOURCES += ../query.h  ../query.cc
 query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../config.h ../config.cc
+query_bench_SOURCES += ../statistics.h ../statistics.cc
 
 query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
 query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la

+ 41 - 1
src/bin/auth/main.cc

@@ -25,7 +25,7 @@
 #include <cassert>
 #include <iostream>
 
-#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
 
 #include <exceptions/exceptions.h>
 
@@ -62,6 +62,10 @@ static bool verbose_mode = false;
 static const string PROGRAM = "Auth";
 static const char* DNSPORT = "5300";
 
+// Note: this value must be greater than 0.
+// TODO: make it configurable via command channel.
+const uint32_t STATISTICS_SEND_INTERVAL_SEC = 60;
+
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
@@ -84,6 +88,12 @@ my_command_handler(const string& command, ConstElementPtr args) {
         answer = createAnswer(0, args);
     } else if (command == "shutdown") {
         io_service.stop();
+    } else if (command == "sendstats") {
+        if (verbose_mode) {
+            cerr << "[b10-auth] command 'sendstats' received" << endl;
+        }
+        assert(auth_server != NULL);
+        auth_server->submitStatistics();
     }
 
     return (answer);
@@ -103,6 +113,12 @@ usage() {
     cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
+
+void
+statisticsTimerCallback(AuthSrv* auth_server) {
+    assert(auth_server != NULL);
+    auth_server->submitStatistics();
+}
 } // end of anonymous namespace
 
 int
@@ -168,7 +184,10 @@ main(int argc, char* argv[]) {
     // XXX: we should eventually pass io_service here.
     Session* cc_session = NULL;
     Session* xfrin_session = NULL;
+    Session* statistics_session = NULL;
+    IntervalTimer* itimer = NULL;
     bool xfrin_session_established = false; // XXX (see Trac #287)
+    bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
     string xfrout_socket_path;
     if (getenv("B10_FROM_BUILD") != NULL) {
@@ -230,12 +249,19 @@ main(int argc, char* argv[]) {
         xfrin_session_established = true;
         cout << "[b10-auth] Xfrin session channel established." << endl;
 
+        statistics_session = new Session(io_service.get_io_service());
+        cout << "[b10-auth] Statistics session channel created." << endl;
+        statistics_session->establish(NULL);
+        statistics_session_established = true;
+        cout << "[b10-auth] Statistics session channel established." << endl;
+
         // XXX: with the current interface to asiolink we have to create
         // auth_server before io_service while Session needs io_service.
         // In a next step of refactoring we should make asiolink independent
         // from auth_server, and create io_service, auth_server, and
         // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
+        auth_server->setStatisticsSession(statistics_session);
 
         // Configure the server.  configureAuthServer() is expected to install
         // all initial configurations, but as a short term workaround we
@@ -245,6 +271,14 @@ main(int argc, char* argv[]) {
         configureAuthServer(*auth_server, config_session->getFullConfig());
         auth_server->updateConfig(ElementPtr());
 
+        // create interval timer instance
+        itimer = new IntervalTimer(io_service);
+        // set up interval timer
+        // register function to send statistics with interval
+        itimer->setupTimer(boost::bind(statisticsTimerCallback, auth_server),
+                           STATISTICS_SEND_INTERVAL_SEC);
+        cout << "[b10-auth] Interval timer to send statistics set." << endl;
+
         cout << "[b10-auth] Server started." << endl;
         io_service.run();
 
@@ -254,10 +288,16 @@ main(int argc, char* argv[]) {
         ret = 1;
     }
 
+    if (statistics_session_established) {
+        statistics_session->disconnect();
+    }
+
     if (xfrin_session_established) {
         xfrin_session->disconnect();
     }
 
+    delete itimer;
+    delete statistics_session;
     delete xfrin_session;
     delete config_session;
     delete cc_session;

+ 162 - 0
src/bin/auth/statistics.cc

@@ -0,0 +1,162 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <auth/statistics.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <sstream>
+#include <iostream>
+
+// TODO: We need a namespace ("auth_server"?) to hold
+// AuthSrv and AuthCounters.
+
+class AuthCountersImpl {
+private:
+    // prohibit copy
+    AuthCountersImpl(const AuthCountersImpl& source);
+    AuthCountersImpl& operator=(const AuthCountersImpl& source);
+public:
+    // References verbose_mode flag in AuthSrvImpl
+    // TODO: Fix this short term workaround for logging
+    // after we have logging framework
+    AuthCountersImpl(const bool& verbose_mode);
+    ~AuthCountersImpl();
+    void inc(const AuthCounters::CounterType type);
+    bool submitStatistics() const;
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+    // Currently for testing purpose only
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+private:
+    std::vector<uint64_t> counters_;
+    isc::cc::AbstractSession* statistics_session_;
+    const bool& verbose_mode_;
+};
+
+AuthCountersImpl::AuthCountersImpl(const bool& verbose_mode) :
+    // initialize counter
+    // size: AuthCounters::COUNTER_TYPES, initial value: 0
+    counters_(AuthCounters::COUNTER_TYPES, 0),
+    statistics_session_(NULL),
+    verbose_mode_(verbose_mode)
+{}
+
+AuthCountersImpl::~AuthCountersImpl()
+{}
+
+void
+AuthCountersImpl::inc(const AuthCounters::CounterType type) {
+    ++counters_.at(type);
+}
+
+bool
+AuthCountersImpl::submitStatistics() const {
+    if (statistics_session_ == NULL) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "session interface for statistics"
+                      << " is not available" << std::endl;
+        }
+        return (false);
+    }
+    std::stringstream statistics_string;
+    statistics_string << "{\"command\": [\"set\","
+                      <<   "{ \"stats_data\": "
+                      <<     "{ \"auth.queries.udp\": "
+                      <<     counters_.at(AuthCounters::COUNTER_UDP_QUERY)
+                      <<     ", \"auth.queries.tcp\": "
+                      <<     counters_.at(AuthCounters::COUNTER_TCP_QUERY)
+                      <<   " }"
+                      <<   "}"
+                      << "]}";
+    isc::data::ConstElementPtr statistics_element =
+        isc::data::Element::fromJSON(statistics_string);
+    try {
+        // group_{send,recv}msg() can throw an exception when encountering
+        // an error, and group_recvmsg() will throw an exception on timeout.
+        // We don't want to kill the main server just due to this, so we
+        // handle them here.
+        const int seq =
+            statistics_session_->group_sendmsg(statistics_element, "Stats");
+        isc::data::ConstElementPtr env, answer;
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "send statistics data" << std::endl;
+        }
+        // TODO: parse and check response from statistics module
+        // currently it just returns empty message
+        statistics_session_->group_recvmsg(env, answer, false, seq);
+    } catch (const isc::cc::SessionError& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "communication error in sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    } catch (const isc::cc::SessionTimeout& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "timeout happened while sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    }
+    return (true);
+}
+
+void
+AuthCountersImpl::setStatisticsSession
+    (isc::cc::AbstractSession* statistics_session)
+{
+    statistics_session_ = statistics_session;
+}
+
+// Currently for testing purpose only
+uint64_t
+AuthCountersImpl::getCounter(const AuthCounters::CounterType type) const {
+    return (counters_.at(type));
+}
+
+AuthCounters::AuthCounters(const bool& verbose_mode) :
+    impl_(new AuthCountersImpl(verbose_mode))
+{}
+
+AuthCounters::~AuthCounters() {
+    delete impl_;
+}
+
+void
+AuthCounters::inc(const AuthCounters::CounterType type) {
+    impl_->inc(type);
+}
+
+bool
+AuthCounters::submitStatistics() const {
+    return (impl_->submitStatistics());
+}
+
+void
+AuthCounters::setStatisticsSession
+    (isc::cc::AbstractSession* statistics_session)
+{
+    impl_->setStatisticsSession(statistics_session);
+}
+
+uint64_t
+AuthCounters::getCounter(const AuthCounters::CounterType type) const {
+    return (impl_->getCounter(type));
+}

+ 146 - 0
src/bin/auth/statistics.h

@@ -0,0 +1,146 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __STATISTICS_H
+#define __STATISTICS_H 1
+
+#include <cc/session.h>
+
+class AuthCountersImpl;
+
+/// \brief Set of query counters.
+///
+/// \c AuthCounters is set of query counters class. It holds query counters
+/// and provides an interface to increment the counter of specified type
+/// (e.g. UDP query, TCP query).
+///
+/// This class also provides a function to send statistics information to
+/// statistics module.
+///
+/// This class is designed to be a part of \c AuthSrv.
+/// Call \c setStatisticsSession() to set a session to communicate with
+/// statistics module like Xfrin session.
+/// Call \c inc() to increment a counter for specific type of query in
+/// the query processing function. use \c enum \c CounterType to specify
+/// the type of query.
+/// Call \c submitStatistics() to submit statistics information to statistics
+/// module with statistics_session, periodically or at a time the command
+/// \c sendstats is received.
+///
+/// We may eventually want to change the structure to hold values that are
+/// not counters (such as concurrent TCP connections), or seperate generic
+/// part to src/lib to share with the other modules.
+///
+/// This class uses pimpl idiom and hides detailed implementation.
+/// This class is constructed on startup of the server, so
+/// construction overhead of this approach should be acceptable.
+///
+/// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
+/// \todo Consider overhead of \c AuthCounters::inc()
+class AuthCounters {
+private:
+    AuthCountersImpl* impl_;
+public:
+    // Enum for the type of counter
+    enum CounterType {
+        COUNTER_UDP_QUERY = 0,  ///< COUNTER_UDP_QUERY: counter for UDP queries
+        COUNTER_TCP_QUERY = 1,  ///< COUNTER_TCP_QUERY: counter for TCP queries
+        COUNTER_TYPES = 2 ///< The number of defined counters
+    };
+    /// The constructor.
+    ///
+    /// \param verbose_mode reference to verbose_mode_ of AuthSrvImpl
+    ///
+    /// This constructor is mostly exception free. But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \todo Fix this short term workaround for logging
+    /// after we have logging framework.
+    ///
+    AuthCounters(const bool& verbose_mode);
+    /// The destructor.
+    ///
+    /// This method never throws an exception.
+    ///
+    ~AuthCounters();
+
+    /// \brief Increment the counter specified by the parameter.
+    ///
+    /// \param type Type of a counter to increment.
+    ///
+    /// \throw std::out_of_range \a type is unknown.
+    ///
+    /// usage: counter.inc(CounterType::COUNTER_UDP_QUERY);
+    /// 
+    void inc(const CounterType type);
+
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This method is desinged to be called periodically
+    /// with \c asio_link::StatisticsSendTimer, or arbitrary
+    /// by the command 'sendstats'.
+    ///
+    /// Note: Set the session to communicate with statistics module
+    /// by \c setStatisticsSession() before calling \c submitStatistics().
+    ///
+    /// This method is mostly exception free (error conditions are
+    /// represented via the return value). But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \return true on success, false on error.
+    ///
+    /// \todo Do not block message handling in auth_srv while submitting
+    /// statistics data.
+    ///
+    bool submitStatistics() const;
+
+    /// \brief Set the session to communicate with Statistics
+    /// module.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and session
+    /// frameworks, at which point the session will probably be passed on
+    /// construction of the server.
+    ///
+    /// Ownership isn't transferred: the caller is responsible for keeping
+    /// this object to be valid while the server object is working and for
+    /// disconnecting the session and destroying the object when the server
+    /// is shutdown.
+    ///
+    /// \param statistics_session A pointer to the session
+    ///
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+    /// \brief Get a value of a counter in the AuthCounters.
+    ///
+    /// This function returns a value of the counter specified by \a type.
+    /// This method never throws an exception.
+    ///
+    /// Note: Currently this function is for testing purpose only.
+    ///
+    /// \param type Type of a counter to get the value of
+    ///
+    /// \return the value of the counter specified by \a type.
+    ///
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+};
+
+#endif // __STATISTICS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 0
src/bin/auth/tests/Makefile.am

@@ -22,10 +22,12 @@ run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../config.h ../config.cc
+run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
+run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 89 - 0
src/bin/auth/tests/auth_srv_unittest.cc

@@ -18,6 +18,7 @@
 #include <datasrc/memory_datasrc.h>
 #include <auth/auth_srv.h>
 #include <testutils/srv_unittest.h>
+#include <auth/statistics.h>
 
 using namespace isc::cc;
 using namespace isc::dns;
@@ -38,7 +39,9 @@ class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
         server.setXfrinSession(&notify_session);
+        server.setStatisticsSession(&statistics_session);
     }
+    MockSession statistics_session;
     MockXfroutClient xfrout;
     AuthSrv server;
     const RRClass rrclass;
@@ -429,4 +432,90 @@ TEST_F(AuthSrvTest, cacheSlots) {
     server.setCacheSlots(0);
     EXPECT_EQ(00, server.getCacheSlots());
 }
+
+// Submit UDP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterUDPNormal) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    // Create UDP message and process.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+    // After processing UDP query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+// Submit TCP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPNormal) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    // Create TCP message and process.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+    // After processing TCP query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// Submit TCP AXFR query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    // On success, the AXFR query has been passed to a separate process,
+    // so we shouldn't have to respond.
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    // After processing TCP AXFR query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// class for queryCounterUnexpected test
+// getProtocol() returns IPPROTO_IP
+class DummyUnknownSocket : public IOSocket {
+public:
+    DummyUnknownSocket() {}
+    virtual int getNative() const { return (0); }
+    virtual int getProtocol() const { return (IPPROTO_IP); }
+};
+
+// function for queryCounterUnexpected test
+// returns a reference to a static object of DummyUnknownSocket
+IOSocket&
+getDummyUnknownSocket() {
+    static DummyUnknownSocket socket;
+    return (socket);
+}
+
+// Submit unexpected type of query and check it throws isc::Unexpected
+TEST_F(AuthSrvTest, queryCounterUnexpected) {
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // Create UDP query packet.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_UDP);
+
+    // Modify the message.
+    delete io_message;
+    endpoint = IOEndpoint::create(IPPROTO_UDP,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    io_message = new IOMessage(request_renderer.getData(),
+                               request_renderer.getLength(),
+                               getDummyUnknownSocket(), *endpoint);
+
+    EXPECT_THROW(server.processMessage(*io_message, parse_message,
+                                       response_obuffer, &dnsserv),
+                 isc::Unexpected);
+}
 }

+ 215 - 0
src/bin/auth/tests/statistics_unittest.cc

@@ -0,0 +1,215 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <auth/statistics.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace {
+
+class AuthCountersTest : public ::testing::Test {
+private:
+    class MockSession : public AbstractSession {
+    public:
+        MockSession() :
+            // by default we return a simple "success" message.
+            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+            throw_session_error_(false), throw_session_timeout_(false)
+        {}
+        virtual void establish(const char* socket_file);
+        virtual void disconnect();
+        virtual int group_sendmsg(ConstElementPtr msg, string group,
+                                  string instance, string to);
+        virtual bool group_recvmsg(ConstElementPtr& envelope,
+                                   ConstElementPtr& msg,
+                                   bool nonblock, int seq);
+        virtual void subscribe(string group, string instance);
+        virtual void unsubscribe(string group, string instance);
+        virtual void startRead(boost::function<void()> read_callback);
+        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
+        virtual bool hasQueuedMsgs() const;
+        virtual void setTimeout(size_t) {}
+        virtual size_t getTimeout() const { return (0); };
+
+        void setThrowSessionError(bool flag);
+        void setThrowSessionTimeout(bool flag);
+
+        void setMessage(ConstElementPtr msg) { msg_ = msg; }
+
+        ConstElementPtr sent_msg;
+        string msg_destination;
+    private:
+        ConstElementPtr msg_;
+        bool throw_session_error_;
+        bool throw_session_timeout_;
+    };
+
+protected:
+    AuthCountersTest() : verbose_mode_(false), counters(verbose_mode_) {
+        counters.setStatisticsSession(&statistics_session_);
+    }
+    ~AuthCountersTest() {
+    }
+    MockSession statistics_session_;
+    bool verbose_mode_;
+    AuthCounters counters;
+};
+
+void
+AuthCountersTest::MockSession::establish(const char*) {}
+
+void
+AuthCountersTest::MockSession::disconnect() {}
+
+void
+AuthCountersTest::MockSession::subscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::unsubscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::startRead(boost::function<void()>)
+{}
+
+int
+AuthCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
+    return (-1);
+}
+
+bool
+AuthCountersTest::MockSession::hasQueuedMsgs() const {
+    return (false);
+}
+
+int
+AuthCountersTest::MockSession::group_sendmsg(ConstElementPtr msg,
+                                              string group, string, string)
+{
+    if (throw_session_error_) {
+        isc_throw(SessionError, "Session Error");
+    }
+    sent_msg = msg;
+    msg_destination = group;
+    return (0);
+}
+
+bool
+AuthCountersTest::MockSession::group_recvmsg(ConstElementPtr&,
+                                              ConstElementPtr& msg, bool, int)
+{
+    if (throw_session_timeout_) {
+        isc_throw(SessionTimeout, "Session Timeout");
+    }
+    msg = msg_;
+    return (true);
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionError(bool flag) {
+    throw_session_error_ = flag;
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionTimeout(bool flag) {
+    throw_session_timeout_ = flag;
+}
+
+TEST_F(AuthCountersTest, incrementUDPCounter) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_UDP_QUERY));
+    // After increment, the counter should be 1.
+    EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementTCPCounter) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_TCP_QUERY));
+    // After increment, the counter should be 1.
+    EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementInvalidCounter) {
+    // Expect to throw isc::InvalidParameter if the type of the counter is
+    // invalid.
+    EXPECT_THROW(counters.inc(AuthCounters::COUNTER_TYPES),
+                 std::out_of_range);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithoutSession) {
+    // Set statistics_session to NULL and call submitStatistics().
+    // Expect to return false.
+    counters.setStatisticsSession(NULL);
+    EXPECT_FALSE(counters.submitStatistics());
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithException) {
+    // Exception SessionError and SessionTimeout will be thrown
+    // while sending statistics data.
+    // Both expect to return false.
+    statistics_session_.setThrowSessionError(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    statistics_session_.setThrowSessionError(false);
+    statistics_session_.setThrowSessionTimeout(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    statistics_session_.setThrowSessionTimeout(false);
+}
+
+TEST_F(AuthCountersTest, submitStatistics) {
+    // Submit statistics data.
+    // Validate if it submits correct data.
+
+    // Counters should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+
+    // UDP query counter is set to 2.
+    counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+    counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+    // TCP query counter is set to 1.
+    counters.inc(AuthCounters::COUNTER_TCP_QUERY);
+    counters.submitStatistics();
+
+    // Destination is "Stats".
+    EXPECT_EQ("Stats", statistics_session_.msg_destination);
+    // Command is "set".
+    EXPECT_EQ("set", statistics_session_.sent_msg->get("command")
+                         ->get(0)->stringValue());
+    ConstElementPtr statistics_data = statistics_session_.sent_msg
+                                          ->get("command")->get(1)
+                                          ->get("stats_data");
+    // UDP query counter is 2 and TCP query counter is 1.
+    EXPECT_EQ(2, statistics_data->get("auth.queries.udp")->intValue());
+    EXPECT_EQ(1, statistics_data->get("auth.queries.tcp")->intValue());
+}
+
+}

+ 87 - 0
src/lib/asiolink/asiolink.cc

@@ -26,6 +26,7 @@
 #include <asio.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 #include <boost/shared_ptr.hpp>
 
@@ -374,4 +375,90 @@ RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
          timeout_, retries_);
 }
 
+class IntervalTimerImpl {
+private:
+    // prohibit copy
+    IntervalTimerImpl(const IntervalTimerImpl& source);
+    IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+    IntervalTimerImpl(IOService& io_service);
+    ~IntervalTimerImpl();
+    void setupTimer(const IntervalTimer::Callback& cbfunc,
+                    const uint32_t interval);
+    void callback(const asio::error_code& error);
+private:
+    // a function to update timer_ when it expires
+    void updateTimer();
+    // a function to call back when timer_ expires
+    IntervalTimer::Callback cbfunc_;
+    // interval in seconds
+    uint32_t interval_;
+    // asio timer
+    asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+    timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
+                              const uint32_t interval)
+{
+    // Interval should not be 0.
+    if (interval == 0) {
+        isc_throw(isc::BadValue, "Interval should not be 0");
+    }
+    // Call back function should not be empty.
+    if (cbfunc.empty()) {
+        isc_throw(isc::InvalidParameter, "Callback function is empty");
+    }
+    cbfunc_ = cbfunc;
+    interval_ = interval;
+    // Set initial expire time.
+    // At this point the timer is not running yet and will not expire.
+    // After calling IOService::run(), the timer will expire.
+    updateTimer();
+    return;
+}
+
+void
+IntervalTimerImpl::updateTimer() {
+    try {
+        // Update expire time to (current time + interval_).
+        timer_.expires_from_now(boost::posix_time::seconds(interval_));
+    } catch (const asio::system_error& e) {
+        isc_throw(isc::Unexpected, "Failed to update timer");
+    }
+    // Reset timer.
+    timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+    // Do not call cbfunc_ in case the timer was cancelled.
+    // The timer will be canelled in the destructor of asio::deadline_timer.
+    if (!cancelled) {
+        cbfunc_();
+        // Set next expire time.
+        updateTimer();
+    }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+    impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+    delete impl_;
+}
+
+void
+IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
+    return (impl_->setupTimer(cbfunc, interval));
+}
+
 }

+ 94 - 0
src/lib/asiolink/asiolink.h

@@ -23,6 +23,7 @@
 #include <unistd.h>             // for some network system calls
 #include <asio/ip/address.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
 
 #include <functional>
 #include <string>
@@ -98,6 +99,7 @@ class io_service;
 namespace asiolink {
 class DNSServiceImpl;
 struct IOServiceImpl;
+struct IntervalTimerImpl;
 
 /// \brief An exception that is thrown if an error occurs within the IO
 /// module.  This is mainly intended to be a wrapper exception class for
@@ -567,6 +569,98 @@ private:
     unsigned retries_;
 };
 
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as
+/// interval timer.
+///
+/// \c setupTimer() sets a timer to expire on (now + interval) and
+/// a call back function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when
+/// it expires.
+///
+/// The function calls the call back function set by \c setupTimer()
+/// and updates the timer to expire in (now + interval) seconds.
+/// The type of call back function is \c void(void).
+/// 
+/// The call back function will not be called if the instance of this
+/// class is destructed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back
+/// is pending causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+///  void function_to_call_back() {
+///      // this function will be called periodically
+///  }
+///  int interval_in_seconds = 1;
+///  IOService io_service;
+///
+///  IntervalTimer intervalTimer(io_service);
+///  intervalTimer.setupTimer(function_to_call_back, interval_in_seconds);
+///  io_service.run();
+/// \endcode
+///
+class IntervalTimer {
+public:
+    /// \name The type of timer callback function
+    typedef boost::function<void()> Callback;
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IntervalTimer(const IntervalTimer& source);
+    IntervalTimer& operator=(const IntervalTimer& source);
+public:
+    /// \brief The constructor with \c IOService.
+    ///
+    /// This constructor may throw a standard exception if
+    /// memory allocation fails inside the method.
+    /// This constructor may also throw \c asio::system_error.
+    ///
+    /// \param io_service A reference to an instance of IOService
+    ///
+    IntervalTimer(IOService& io_service);
+
+    /// \brief The destructor.
+    ///
+    /// This destructor never throws an exception.
+    ///
+    /// On the destruction of this class the timer will be canceled
+    /// inside \c asio::deadline_timer.
+    ///
+    ~IntervalTimer();
+    //@}
+
+    /// \brief Register timer callback function and interval.
+    ///
+    /// This function sets callback function and interval in seconds.
+    /// Timer will actually start after calling \c IOService::run().
+    ///
+    /// \param cbfunc A reference to a function \c void(void) to call back
+    /// when the timer is expired (should not be an empty functor)
+    /// \param interval Interval in seconds (greater than 0)
+    ///
+    /// Note: IntervalTimer will not pass \c asio::error_code to
+    /// call back function. In case the timer is cancelled, the function
+    /// will not be called.
+    ///
+    /// \throw isc::InvalidParameter cbfunc is empty
+    /// \throw isc::BadValue interval is 0
+    /// \throw isc::Unexpected ASIO library error
+    ///
+    void setupTimer(const Callback& cbfunc, const uint32_t interval);
+private:
+    IntervalTimerImpl* impl_;
+};
+
 }      // asiolink
 #endif // __ASIOLINK_H
 

+ 247 - 0
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -21,6 +21,7 @@
 
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 #include <gtest/gtest.h>
 
@@ -54,6 +55,9 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1";
 // two octets encode the length of the rest of the data.  This is crucial
 // for the tests below.
 const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+    boost::posix_time::milliseconds(50);
 
 TEST(IOAddressTest, fromText) {
     IOAddress io_address_v4("192.0.2.1");
@@ -710,4 +714,247 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
     EXPECT_EQ(3, num);
 }
 
+// This fixture is for testing IntervalTimer. Some callback functors are 
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+    IntervalTimerTest() : io_service_() {};
+    ~IntervalTimerTest() {}
+    class TimerCallBack : public std::unary_function<void, void> {
+    public:
+        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+        void operator()() const {
+            test_obj_->timer_called_ = true;
+            test_obj_->io_service_.stop();
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCounter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+            counter_ = 0;
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+        int counter_;
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+                                   IntervalTimer* timer,
+                                   TimerCallBackCounter& counter)
+            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Store the value of counter_.counter_.
+                prev_counter_ = counter_.counter_;
+                delete timer_;
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // Stop io_service to stop all timers.
+                test_obj_->io_service_.stop();
+                // Compare the value of counter_.counter_ with stored one.
+                // If TimerCallBackCounter was not called (expected behavior),
+                // they are same.
+                if (counter_.counter_ == prev_counter_) {
+                    test_obj_->timer_cancel_success_ = true;
+                }
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer* timer_;
+        TimerCallBackCounter& counter_;
+        int count_;
+        int prev_counter_;
+    };
+    class TimerCallBackOverwriter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+                                IntervalTimer& timer)
+            : test_obj_(test_obj), timer_(timer), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Call setupTimer() to update callback function
+                // to TimerCallBack.
+                test_obj_->timer_called_ = false;
+                timer_.setupTimer(TimerCallBack(test_obj_), 1);
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // If it reaches here, re-setupTimer() is failed (unexpected).
+                // We should stop here.
+                test_obj_->io_service_.stop();
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer& timer_;
+        int count_;
+    };
+protected:
+    IOService io_service_;
+    bool timer_called_;
+    bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    IntervalTimer itimer(io_service_);
+    // expect throw if call back function is empty
+    EXPECT_THROW(itimer.setupTimer(IntervalTimer::Callback(), 1),
+                     isc::InvalidParameter);
+    // expect throw if interval is 0
+    EXPECT_THROW(itimer.setupTimer(TimerCallBack(this), 0), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    // Then run IOService and test if the callback function is called.
+    IntervalTimer itimer(io_service_);
+    timer_called_ = false;
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    // setup timer
+    itimer.setupTimer(TimerCallBack(this), 1);
+    io_service_.run();
+    // reaches here after timer expired
+    // delta: difference between elapsed time and 1 second
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::seconds(1);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect TimerCallBack is called; timer_called_ is true
+    EXPECT_TRUE(timer_called_);
+    // expect interval is 1 second +/- TIMER_MARGIN_MSEC.
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+    // Note: This test currently takes 6 seconds. The timer should have
+    // finer granularity and timer periods in this test should be shorter
+    // in the future.
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // The call back function will not be called after the timer is
+    // destructed.
+    //
+    // There are two timers:
+    //  itimer_counter (A)
+    //   (Calls TimerCallBackCounter)
+    //     - increments internal counter in callback function
+    //  itimer_canceller (B)
+    //   (Calls TimerCallBackCancelDeleter)
+    //     - first time of callback, it stores the counter value of
+    //       callback_canceller and destructs itimer_counter
+    //     - second time of callback, it compares the counter value of
+    //       callback_canceller with stored value
+    //       if they are same the timer was not called; expected result
+    //       if they are different the timer was called after destructed
+    //
+    //     0  1  2  3  4  5  6 (s)
+    // (A) i-----+--x
+    //              ^
+    //              |destruct itimer_counter
+    // (B) i--------+--------s
+    //                       ^stop io_service
+    //                        and test itimer_counter have been stopped
+    //
+
+    // itimer_counter will be deleted in TimerCallBackCancelDeleter
+    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+    IntervalTimer itimer_canceller(io_service_);
+    timer_cancel_success_ = false;
+    TimerCallBackCounter callback_canceller(this);
+    itimer_counter->setupTimer(callback_canceller, 2);
+    itimer_canceller.setupTimer(
+        TimerCallBackCancelDeleter(this, itimer_counter,
+                                   callback_canceller),
+        3);
+    io_service_.run();
+    EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+    // Note: This test currently takes 4 seconds. The timer should have
+    // finer granularity and timer periods in this test should be shorter
+    // in the future.
+
+    // Calling setupTimer() multiple times updates call back function
+    // and interval.
+    //
+    // There are two timers:
+    //  itimer (A)
+    //   (Calls TimerCallBackCounter / TimerCallBack)
+    //     - increments internal counter in callback function
+    //       (TimerCallBackCounter)
+    //       interval: 2 seconds
+    //     - io_service_.stop() (TimerCallBack)
+    //       interval: 1 second
+    //  itimer_overwriter (B)
+    //   (Calls TimerCallBackOverwriter)
+    //     - first time of callback, it calls setupTimer() to change
+    //       call back function and interval of itimer to
+    //       TimerCallBack / 1 second
+    //       after 3 + 1 seconds from the beginning of this test,
+    //       TimerCallBack() will be called and io_service_ stops.
+    //     - second time of callback, it means the test fails.
+    //
+    //     0  1  2  3  4  5  6 (s)
+    // (A) i-----+--C--s
+    //              ^  ^stop io_service
+    //              |change call back function
+    // (B) i--------+--------S
+    //                       ^(stop io_service on fail)
+    //
+
+    IntervalTimer itimer(io_service_);
+    IntervalTimer itimer_overwriter(io_service_);
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    itimer.setupTimer(TimerCallBackCounter(this), 2);
+    itimer_overwriter.setupTimer(TimerCallBackOverwriter(this, itimer), 3);
+    io_service_.run();
+    // reaches here after timer expired
+    // if interval is updated, it takes
+    //   3 seconds for TimerCallBackOverwriter
+    //   + 1 second for TimerCallBack (stop)
+    //   = 4 seconds.
+    // otherwise (test fails), it takes
+    //   3 seconds for TimerCallBackOverwriter
+    //   + 3 seconds for TimerCallBackOverwriter (stop)
+    //   = 6 seconds.
+    // delta: difference between elapsed time and 3 + 1 seconds
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::seconds(3 + 1);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect callback function is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // expect interval is updated
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
 }