Browse Source

[master] Merge branch 2955 which adds D2Process and DProcessBase
to bin/src/D2.

Thomas Markwalder 12 years ago
parent
commit
dbe4772246

+ 5 - 0
src/bin/d2/Makefile.am

@@ -48,12 +48,17 @@ pkglibexec_PROGRAMS = b10-d2
 
 b10_d2_SOURCES  = main.cc
 b10_d2_SOURCES += d2_log.cc d2_log.h
+b10_d2_SOURCES += d_process.h 
+b10_d2_SOURCES += d2_process.cc d2_process.h
 
 nodist_b10_d2_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 
 b10_d2_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
 b10_d2_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_d2_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_d2_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_d2_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 
 b10_d2dir = $(pkgdatadir)
 b10_d2_DATA = d2.spec

+ 29 - 9
src/bin/d2/d2_messages.mes

@@ -14,15 +14,35 @@
 
 $NAMESPACE isc::d2
 
-% D2_STARTING : process starting
-This is a debug message issued during a D2 process startup.
+% D2CTL_STARTING DHCP-DDNS controller starting, pid: %1
+This is an informational message issued when controller for DHCP-DDNS 
+service first starts.
 
-% D2_START_INFO pid: %1, verbose: %2, standalone: %3
-This is a debug message issued during the D2 process startup.
-It lists some information about the parameters with which the
-process is running.
+% D2CTL_STOPPING DHCP-DDNS controller is exiting
+This is an informational message issued when the controller is exiting 
+following a shut down (normal or otherwise) of the DDHCP-DDNS process.
 
-% D2_SHUTDOWN : process is performing a normal shutting down
-This is a debug message issued when a D2 process shuts down
-normally in response to command to stop.
+% D2PRC_SHUTDOWN DHCP-DDNS process is performing a normal shut down
+This is a debug message issued when the service process has been instructed
+to shut down by the controller.
+
+% D2PRC_RUN_ENTER process has entered the event loop
+This is a debug message issued when the D2 process enters it's
+run method. 
+
+% D2PRC_RUN_EXIT process is exiting the event loop
+This is a debug message issued when the D2 process exits the
+in event loop. 
+
+% D2PRC_FAILED process experienced a fatal error: %1
+This is a debug message issued when the D2 process encounters an
+unrecoverable error from within the event loop.
+
+% D2PRC_CONFIGURE new configuration received: %1
+This is a debug message issued when the D2 process configure method
+has been invoked.
+
+% D2PRC_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the D2 process command method
+has been invoked.
 

+ 89 - 0
src/bin/d2/d2_process.cc

@@ -0,0 +1,89 @@
+// Copyright (C) 2013  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 <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d2_process.h>
+
+using namespace asio;
+
+namespace isc {
+namespace d2 {
+
+D2Process::D2Process(const char* name, IOServicePtr io_service) 
+    : DProcessBase(name, io_service) {
+};
+
+void
+D2Process::init() {
+};
+
+int
+D2Process::run() {
+    // Until shut down or an fatal error occurs, wait for and
+    // execute a single callback. This is a preliminary implementation
+    // that is likely to evolve as development progresses.
+    // To use run(), the "managing" layer must issue an io_service::stop 
+    // or the call to run will continue to block, and shutdown will not
+    // occur.
+    LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_ENTER);
+    IOServicePtr& io_service = getIoService();
+    while (!shouldShutdown()) {
+        try {
+            io_service->run_one();
+        } catch (const std::exception& ex) {
+            LOG_FATAL(d2_logger, D2PRC_FAILED).arg(ex.what());
+            return (EXIT_FAILURE); 
+        }
+    }
+
+    LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_EXIT);
+    return (EXIT_SUCCESS);
+};
+
+int 
+D2Process::shutdown() {
+    LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_SHUTDOWN);
+    setShutdownFlag(true);
+    return (0);
+}    
+
+isc::data::ConstElementPtr 
+D2Process::configure(isc::data::ConstElementPtr config_set) {
+    // @TODO This is the initial implementation which simply accepts
+    // any content in config_set as valid.  This is sufficient to 
+    // allow participation as a BIND10 module, while D2 configuration support
+    // is being developed.
+    LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC, 
+              D2PRC_CONFIGURE).arg(config_set->str());
+
+    return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr 
+D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
+    // @TODO This is the initial implementation.  If and when D2 is extended
+    // to support its own commands, this implementation must change. Otherwise
+    // it should reject all commands as it does now. 
+    LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC, 
+              D2PRC_COMMAND).arg(command).arg(args->str());
+
+    return (isc::config::createAnswer(1, "Unrecognized command:" + command));
+}
+
+D2Process::~D2Process() {
+};
+
+}; // namespace isc::d2 
+}; // namespace isc

+ 98 - 0
src/bin/d2/d2_process.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2013  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 D2_PROCESS_H
+#define D2_PROCESS_H
+
+#include <d2/d_process.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief @TODO DHCP-DDNS Application Process 
+///
+/// D2Process provides the top level application logic for DHCP-driven DDNS 
+/// update processing.  It provides the asynchronous event processing required 
+/// to receive DNS mapping change requests and carry them out.   
+/// It implements the DProcessBase interface, which structures it such that it
+/// is a managed "application", controlled by a management layer. 
+
+class D2Process : public DProcessBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param name name is a text label for the process. Generally used
+    /// in log statements, but otherwise arbitrary. 
+    /// @param io_service is the io_service used by the caller for
+    /// asynchronous event handling.
+    ///
+    /// @throw DProcessBaseError is io_service is NULL. 
+    D2Process(const char* name, IOServicePtr io_service);
+
+    /// @brief Will be used after instantiation to perform initialization 
+    /// unique to D2. This will likely include interactions with QueueMgr and 
+    /// UpdateMgr, to prepare for request receipt and processing.
+    virtual void init();
+
+    /// @brief Implements the process's event loop. 
+    /// The initial implementation is quite basic, surrounding calls to 
+    /// io_service->runOne() with a test of the shutdown flag.
+    /// Once invoked, the method will continue until the process itself is 
+    /// exiting due to a request to shutdown or some anomaly forces an exit.   
+    /// @return  returns 0 upon a successful, "normal" termination, non
+    /// zero to indicate an abnormal termination.    
+    virtual int run();
+
+    // @TODO need brief
+    virtual int shutdown();
+
+    // @TODO need brief
+    /// @brief Processes the given configuration. 
+    /// 
+    /// This method may be called multiple times during the process lifetime.
+    /// Certainly once during process startup, and possibly later if the user
+    /// alters configuration. This method must not throw, it should catch any
+    /// processing errors and return a success or failure answer as described
+    /// below. 
+    ///
+    /// @param config_set a new configuration (JSON) for the process
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome. 
+    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+                                                 config_set);
+
+    // @TODO need brief
+    /// @brief Processes the given command. 
+    /// 
+    /// This method is called to execute any custom commands supported by the 
+    /// process. This method must not throw, it should catch any processing 
+    /// errors and return a success or failure answer as described below.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command. 
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.  
+    virtual isc::data::ConstElementPtr command(const std::string& command, 
+                                               isc::data::ConstElementPtr args);
+    // @TODO need brief
+    virtual ~D2Process();
+};
+
+}; // namespace isc::d2 
+}; // namespace isc
+
+#endif

+ 151 - 0
src/bin/d2/d_process.h

@@ -0,0 +1,151 @@
+// Copyright (C) 2013  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 D_PROCESS_H
+#define D_PROCESS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the process encountered an operational error.
+class DProcessBaseError : public isc::Exception {
+public:
+    DProcessBaseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Application Process Interface
+///
+/// DProcessBase is an abstract class represents the primary "application" 
+/// level object in a "managed" asynchronous application. It provides a uniform 
+/// interface such that a managing layer can construct, intialize, and start
+/// the application's event loop.  The event processing is centered around the
+/// use of isc::asiolink::io_service. The io_service is shared between the 
+/// the managing layer and the DProcessBase.  This allows management layer IO 
+/// such as directives to be sensed and handled, as well as processing IO 
+/// activity specific to the application.  In terms of management layer IO,
+/// there are methods shutdown, configuration updates, and commands unique
+/// to the application.  
+class DProcessBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param name name is a text label for the process. Generally used
+    /// in log statements, but otherwise arbitrary. 
+    /// @param io_service is the io_service used by the caller for
+    /// asynchronous event handling.
+    ///
+    /// @throw DProcessBaseError is io_service is NULL. 
+    DProcessBase(const char* name, IOServicePtr io_service) : name_(name),
+        io_service_(io_service), shut_down_flag_(false) {
+
+        if (!io_service_) {
+            isc_throw (DProcessBaseError, "IO Service cannot be null");
+        }
+    };
+
+    /// @brief May be used after instantiation to perform initialization unique
+    /// to application. It must be invoked prior to invoking run. This would 
+    /// likely include the creation of additional IO sources and their 
+    /// integration into the io_service. 
+    virtual void init() = 0; 
+
+    /// @brief Implements the process's event loop. In its simplest form it 
+    /// would an invocation io_service_->run().  This method should not exit 
+    /// until the process itself is exiting due to a request to shutdown or 
+    /// some anomaly is forcing an exit.   
+    /// @return  returns EXIT_SUCCESS upon a successful, normal termination, 
+    /// and EXIT_FAILURE to indicate an abnormal termination.    
+    virtual int run() = 0; 
+
+    /// @brief Implements the process's shutdown processing. When invoked, it 
+    /// should ensure that the process gracefully exits the run method. 
+    virtual int shutdown() = 0;
+
+    /// @brief Processes the given configuration. 
+    /// 
+    /// This method may be called multiple times during the process lifetime.
+    /// Certainly once during process startup, and possibly later if the user
+    /// alters configuration. This method must not throw, it should catch any
+    /// processing errors and return a success or failure answer as described
+    /// below. 
+    ///
+    /// @param config_set a new configuration (JSON) for the process
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome. 
+    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+                                                 config_set) = 0; 
+
+    /// @brief Processes the given command. 
+    /// 
+    /// This method is called to execute any custom commands supported by the 
+    /// process. This method must not throw, it should catch any processing 
+    /// errors and return a success or failure answer as described below.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command. 
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.  
+    virtual isc::data::ConstElementPtr command(
+            const std::string& command, isc::data::ConstElementPtr args) = 0; 
+
+    /// @brief Destructor 
+    virtual ~DProcessBase(){};
+
+    bool shouldShutdown() { 
+        return (shut_down_flag_); 
+    }
+
+    void setShutdownFlag(bool value) { 
+        shut_down_flag_ = value; 
+    }
+
+    const std::string& getName() const {
+        return (name_);
+    }
+
+    IOServicePtr& getIoService() {
+        return (io_service_);
+    }
+
+private:
+    /// @brief Text label for the process. Generally used in log statements, 
+    /// but otherwise can be arbitrary. 
+    std::string name_;
+
+    /// @brief The IOService to be used for asynchronous event handling. 
+    IOServicePtr io_service_;
+
+    /// @brief Boolean flag set when shutdown has been requested.
+    bool shut_down_flag_;
+};
+
+/// @brief Defines a shared pointer to DProcessBase.
+typedef boost::shared_ptr<DProcessBase> DProcessBasePtr;
+
+}; // namespace isc::d2 
+}; // namespace isc
+
+#endif

+ 3 - 5
src/bin/d2/main.cc

@@ -82,16 +82,14 @@ main(int argc, char* argv[]) {
                          ((verbose_mode && stand_alone)
                            ? isc::log::DEBUG : isc::log::INFO),
                          isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
-    LOG_INFO(d2_logger, D2_STARTING);
-    LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2_START_INFO)
-              .arg(getpid()).arg(verbose_mode ? "yes" : "no")
-              .arg(stand_alone ? "yes" : "no" );
+
+    LOG_INFO(d2_logger, D2CTL_STARTING);
 
     // For now we will sleep awhile to simulate doing something.
     // Without at least a sleep, the process will start, exit and be
     // restarted by Bind10/Init endlessley in a rapid succession.
     sleep(1000);
-    LOG_INFO(d2_logger, D2_SHUTDOWN);
+    LOG_INFO(d2_logger, D2CTL_STOPPING);
     return (EXIT_SUCCESS);
 }
 

+ 6 - 0
src/bin/d2/tests/Makefile.am

@@ -52,7 +52,10 @@ if HAVE_GTEST
 TESTS += d2_unittests
 
 d2_unittests_SOURCES = ../d2_log.h ../d2_log.cc
+d2_unittests_SOURCES += ../d_process.h 
+d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -60,6 +63,9 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 d2_unittests_LDADD = $(GTEST_LDADD)
 d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 166 - 0
src/bin/d2/tests/d2_process_unittests.cc

@@ -0,0 +1,166 @@
+// Copyright (C) 2013  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 <config/ccsession.h>
+#include <d2/d2_process.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief D2Process test fixture class
+class D2ProcessTest : public ::testing::Test {
+public:
+
+    /// @brief Static instance accessible via test callbacks.
+    static DProcessBasePtr process_;
+
+    /// @brief Constructor
+    D2ProcessTest() {
+        io_service_.reset(new isc::asiolink::IOService());
+        process_.reset(new D2Process("TestProcess", io_service_));
+    }
+
+    /// @brief Destructor 
+    ~D2ProcessTest() {
+        io_service_.reset();
+        process_.reset();
+    }
+
+    /// @brief Callback that will invoke shutdown method.
+    static void genShutdownCallback() {
+        process_->shutdown();
+    }
+
+    /// @brief Callback that throws an exception.
+    static void genFatalErrorCallback() {
+        isc_throw (DProcessBaseError, "simulated fatal error");
+    }
+
+    /// @brief IOService for event processing. Fills in for IOservice
+    /// supplied by management layer.
+    IOServicePtr io_service_;
+};
+
+// Define the static process instance
+DProcessBasePtr D2ProcessTest::process_;
+
+
+/// @brief Verifies D2Process constructor behavior.
+/// 1. Verifies that constructor fails with an invalid IOService
+/// 2. Verifies that constructor succeeds with a valid IOService
+TEST(D2Process, construction) {
+    // Verify that the constructor will fail if given an empty
+    // io service.
+    IOServicePtr lcl_io_service;
+    EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
+
+    // Verify that the constructor succeeds with a valid io_service
+    lcl_io_service.reset(new isc::asiolink::IOService());
+    ASSERT_NO_THROW (D2Process("TestProcess", lcl_io_service));
+}
+
+/// @brief Verifies basic configure method behavior.
+// @TODO This test is simplistic and will need to be augmented
+// as configuration ability is implemented.
+TEST_F(D2ProcessTest, configure) {
+    // Verify that given a configuration "set", configure returns
+    // a successful response.
+    int rcode = -1;
+    string config = "{ \"test-value\": 1000 } ";
+    isc::data::ElementPtr json = isc::data::Element::fromJSON(config);
+    isc::data::ConstElementPtr answer = process_->configure(json); 
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(0, rcode);
+}
+
+/// @brief Verifies basic command method behavior. 
+// @TODO IF the D2Process is extended to support extra commands
+// this testing will need to augmented accordingly.
+TEST_F(D2ProcessTest, command) {
+    // Verfiy that the process will process unsupported command and
+    // return a failure response.
+    int rcode = -1;
+    string args = "{ \"arg1\": 77 } ";
+    isc::data::ElementPtr json = isc::data::Element::fromJSON(args);
+    isc::data::ConstElementPtr answer = 
+                                    process_->command("bogus_command", json); 
+    parseAnswer(rcode, answer);
+    EXPECT_EQ(1, rcode);
+}
+
+/// @brief Verifies that an "external" call to shutdown causes 
+/// the run method to exit gracefully with a return value of EXIT_SUCCESS.
+TEST_F(D2ProcessTest, normalShutdown) {
+    // Use an asiolink IntervalTimer and callback to generate the
+    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+    isc::asiolink::IntervalTimer timer(*io_service_);
+    timer.setup(genShutdownCallback, 2 * 1000);
+
+    // Record start time, and invoke run().
+    ptime start = microsec_clock::universal_time();
+    int rcode = process_->run();
+
+    // Record stop time.
+    ptime stop = microsec_clock::universal_time();
+
+    // Verify normal shutdown status.
+    EXPECT_EQ(EXIT_SUCCESS, rcode);
+
+    // Verify that duration of the run invocation is the same as the
+    // timer duration.  This demonstrates that the shutdown was driven
+    // by an io_service event and callback.
+    time_duration elapsed = stop - start;
+    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 && 
+                elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Verifies that an "uncaught" exception thrown during event loop 
+/// processing is treated as a fatal error.
+TEST_F(D2ProcessTest, fatalErrorShutdown) {
+    // Use an asiolink IntervalTimer and callback to generate the
+    // the exception.  (Note IntervalTimer setup is in milliseconds).
+    isc::asiolink::IntervalTimer timer(*io_service_);
+    timer.setup(genFatalErrorCallback, 2 * 1000);
+
+    // Record start time, and invoke run().
+    ptime start = microsec_clock::universal_time();
+    int rcode = process_->run();
+
+    // Record stop time.
+    ptime stop = microsec_clock::universal_time();
+
+    // Verify failure status.
+    EXPECT_EQ(EXIT_FAILURE, rcode);
+
+    // Verify that duration of the run invocation is the same as the
+    // timer duration.  This demonstrates that the anomaly occurred
+    // during io callback processing. 
+    time_duration elapsed = stop - start;
+    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 && 
+                elapsed.total_milliseconds() <= 2100);
+}
+
+} // end of anonymous namespace

+ 1 - 1
src/bin/d2/tests/d2_test.py

@@ -161,7 +161,7 @@ class TestD2Daemon(unittest.TestCase):
         # soon enough to catch it.
         (returncode, output, error) = self.runCommand(["../b10-d2", "-s"])
         output_text = str(output) + str(error)
-        self.assertEqual(output_text.count("D2_STARTING"), 1)
+        self.assertEqual(output_text.count("D2CTL_STARTING"), 1)
 
 if __name__ == '__main__':
     unittest.main()