Parcourir la source

[2395] Created the initial, bare-bones implementation DHCP-DDNS service
process class, D2Process, and the abstract class from which it derives,
DProcess. This class provides DHCP-DDNS specific event loop and business
logic.

The following new files have been added:

src/bin/d2/d_process.h - defines the DProcess base class
src/bin/d2/d2_process.h - defines the D2Process class
src/bin/d2/d2_process.cc - implements the D2Process class
src/bin/d2/tests/d2_process_unittests.cc - initial unit tests

Thomas Markwalder il y a 12 ans
Parent
commit
cddad16de7

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

@@ -0,0 +1,88 @@
+// 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) 
+    : DProcess(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);
+    while (!shut_down_) {
+        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);
+    shut_down_ = 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 DProcess interface, which structures it such that it
+/// is a managed "application", controlled by a management layer. 
+
+class D2Process : public DProcess {
+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 DProcessError 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

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

@@ -0,0 +1,135 @@
+// 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 DProcessError : public isc::Exception {
+public:
+    DProcessError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Application Process Interface
+///
+/// DProcess is an abstract class represents the primary "application" level  
+/// object in a "managed" asyncrhonous 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 DProcess.  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 fo management layer IO,
+/// there are methods shutdown, configuration updates, and commands unique
+/// to the application.  
+class DProcess {
+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 DProcessError is io_service is NULL. 
+    DProcess(const char* name, IOServicePtr io_service) : name_(name),
+        io_service_(io_service), shut_down_(false) {
+
+        if (!io_service_) {
+            isc_throw (DProcessError, "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 ~DProcess(){};
+
+protected:
+    /// @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_;
+};
+
+/// @brief Defines a shared pointer to DProcess.
+typedef boost::shared_ptr<DProcess> DProcessPtr;
+
+}; // namespace isc::d2 
+}; // namespace isc
+
+#endif

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

@@ -0,0 +1,161 @@
+// 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 DProcessPtr 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() {
+        D2ProcessTest::process_->shutdown();
+    }
+
+    /// @brief Callback that throws an exception.
+    static void genFatalErrorCallback() {
+        isc_throw (DProcessError, "simulated fatal error");
+    }
+
+    /// @brief IOService for event processing. Fills in for IOservice
+    /// supplied by management layer.
+    IOServicePtr io_service_;
+};
+
+
+/// @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), DProcessError);
+
+    // 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. 
+    isc::asiolink::IntervalTimer timer(*io_service_);
+    timer.setup(genShutdownCallback, 2*1000);
+
+    // Record start time, and invoke run().
+    ptime start = second_clock::universal_time();
+    int rcode = process_->run();
+
+    // Record stop time.
+    ptime stop = second_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_EQ(2L, elapsed.seconds());
+}
+
+/// @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.
+    isc::asiolink::IntervalTimer timer(*io_service_);
+    timer.setup(genFatalErrorCallback, 2*1000);
+
+    // Record start time, and invoke run().
+    ptime start = second_clock::universal_time();
+    int rcode = process_->run();
+
+    // Record stop time.
+    ptime stop = second_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_EQ(2L, elapsed.seconds());
+}
+
+} // end of anonymous namespace