diff options
Diffstat (limited to 'src/bin/d2/tests/d2_process_unittests.cc')
-rw-r--r-- | src/bin/d2/tests/d2_process_unittests.cc | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc new file mode 100644 index 0000000..4dda275 --- /dev/null +++ b/src/bin/d2/tests/d2_process_unittests.cc @@ -0,0 +1,693 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_service.h> +#include <cc/command_interpreter.h> +#include <d2srv/testutils/nc_test_utils.h> +#include <d2/d2_process.h> +#include <d2/tests/test_configured_libraries.h> +#include <dhcp_ddns/ncr_io.h> +#include <process/testutils/d_test_stubs.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <gtest/gtest.h> + +#include <functional> +#include <sstream> + +using namespace std; +using namespace isc; +using namespace isc::config; +using namespace isc::d2; +using namespace isc::data; +using namespace isc::process; +using namespace boost::posix_time; + +namespace { + +/// @brief Valid configuration containing an unavailable IP address. +const char* bad_ip_d2_config = "{ " + "\"ip-address\" : \"1.1.1.1\" , " + "\"port\" : 5031, " + "\"tsig-keys\": [" + "{ \"name\": \"d2_key.example.com\" , " + " \"algorithm\": \"HMAC-MD5\" ," + " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" " + "} ]," + "\"forward-ddns\" : {" + "\"ddns-domains\": [ " + "{ \"name\": \"example.com\" , " + " \"key-name\": \"d2_key.example.com\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.101\" } " + "] } ] }, " + "\"reverse-ddns\" : {" + "\"ddns-domains\": [ " + "{ \"name\": \" 0.168.192.in.addr.arpa.\" , " + " \"key-name\": \"d2_key.example.com\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.101\" , " + " \"port\": 100 } ] } " + "] } }"; + +/// @brief D2Process test fixture class +//class D2ProcessTest : public D2Process, public ::testing::Test { +class D2ProcessTest : public D2Process, public ConfigParseTest { +public: + + /// @brief Constructor + D2ProcessTest() : + D2Process("d2test", + asiolink::IOServicePtr(new isc::asiolink::IOService())) { + } + + /// @brief Destructor + virtual ~D2ProcessTest() { + } + + /// @brief Callback that will invoke shutdown method. + void genShutdownCallback() { + shutdown(ConstElementPtr()); + } + + /// @brief Callback that throws an exception. + void genFatalErrorCallback() { + isc_throw (DProcessBaseError, "simulated fatal error"); + } + + /// @brief Reconfigures and starts the queue manager given a configuration. + /// + /// This method emulates the reception of a new configuration and should + /// conclude with the Queue manager placed in the RUNNING state. + /// + /// @param config is the configuration to use + /// + /// @return Returns AssertionSuccess if the queue manager was successfully + /// reconfigured, AssertionFailure otherwise. + ::testing::AssertionResult runWithConfig(const char* config) { + int rcode = -1; + // Convert the string configuration into an Element set. + ::testing::AssertionResult res = fromJSON(config); + if (res != ::testing::AssertionSuccess()) { + return res; + } + + ConstElementPtr answer = configure(config_set_, false); + ConstElementPtr comment; + comment = isc::config::parseAnswer(rcode, answer); + + if (rcode) { + return (::testing::AssertionFailure(::testing::Message() << + "configure() failed: " + << comment)); + } + + // Must call checkQueueStatus, to cause queue manager to reconfigure + // and start. + checkQueueStatus(); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + // If queue manager isn't in the RUNNING state, return failure. + if (D2QueueMgr::RUNNING != queue_mgr->getMgrState()) { + return (::testing::AssertionFailure(::testing::Message() << + "queue manager did not start")); + } + + // Good to go. + return (::testing::AssertionSuccess()); + } + + /// @brief Checks if shutdown criteria would be met given a shutdown type. + /// + /// This method sets the D2Process shutdown type to the given value, and + /// calls the canShutdown() method, returning its return value. + /// + /// @return Returns the boolean result canShutdown. + bool checkCanShutdown(ShutdownType shutdown_type) { + setShutdownType(shutdown_type); + return (canShutdown()); + } + + /// @brief Replaces %LIBRARY% with specified library name. + /// + /// @param config input config text (should contain "%LIBRARY%" string). + /// @param lib_name %LIBRARY% will be replaced with that name. + /// @return configuration text with library name replaced. + string pathReplacer(const char* config, const char* lib_name) { + string txt(config); + txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name)); + return (txt); + } +}; + +/// @brief Verifies D2Process construction behavior. +/// 1. Verifies that constructor fails with an invalid IOService +/// 2. Verifies that constructor succeeds with a valid IOService +/// 3. Verifies that all managers are accessible +TEST(D2Process, construction) { + // Verify that the constructor will fail if given an empty + // io service. + asiolink::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)); + + // Verify that the configuration, queue, and update managers + // are all accessible after construction. + D2Process d2process("TestProcess", lcl_io_service); + + D2CfgMgrPtr cfg_mgr = d2process.getD2CfgMgr(); + ASSERT_TRUE(cfg_mgr); + + D2QueueMgrPtr queue_mgr = d2process.getD2QueueMgr(); + ASSERT_TRUE(queue_mgr); + + const D2UpdateMgrPtr& update_mgr = d2process.getD2UpdateMgr(); + ASSERT_TRUE(update_mgr); +} + +/// @brief Verifies basic configure method behavior. +/// This test primarily verifies that upon receipt of a new configuration, +/// D2Process will reconfigure the queue manager if the configuration is valid, +/// or leave queue manager unaffected if not. Currently, the queue manager is +/// only D2 component that must adapt to new configurations. Other components, +/// such as Transactions will be unaffected as they are transient and use +/// whatever configuration was in play at the time they were created. +/// If other components need to provide "dynamic" configuration responses, +/// those tests would need to be added. +TEST_F(D2ProcessTest, configure) { + // Verify the queue manager is not yet initialized. + D2QueueMgrPtr queue_mgr = getD2QueueMgr(); + ASSERT_TRUE(queue_mgr); + ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState()); + + // Verify that reconfigure queue manager flag is false. + ASSERT_FALSE(getReconfQueueFlag()); + + // Create a valid configuration set from text config. + ASSERT_TRUE(fromJSON(valid_d2_config)); + + // Invoke configure() with a valid D2 configuration. + ConstElementPtr answer = configure(config_set_, false); + + // Verify that configure result is success and reconfigure queue manager + // flag is true. + ASSERT_TRUE(checkAnswer(answer, 0)); + ASSERT_TRUE(getReconfQueueFlag()); + + // Call checkQueueStatus, to cause queue manager to reconfigure and start. + checkQueueStatus(); + + // Verify that queue manager is now in the RUNNING state, and flag is false. + ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); + ASSERT_FALSE(getReconfQueueFlag()); + + // Create an invalid configuration set from text config. + ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } ")); + + // Invoke configure() with the invalid configuration. + answer = configure(config_set_, false); + + // Verify that configure result is a success, as extra parameters are + // ignored. the reconfigure flag is false, and that the queue manager is + // still running. + ASSERT_TRUE(checkAnswer(answer, 0)); + EXPECT_TRUE(getReconfQueueFlag()); + EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); + + // Finally, try with an invalid configuration. + // Create an invalid configuration set from text config. + ASSERT_TRUE(fromJSON("{ \"ip-address\": \"950 Charter St.\" } ")); + answer = configure(config_set_, false); + ASSERT_TRUE(checkAnswer(answer, 1)); + EXPECT_FALSE(getReconfQueueFlag()); + EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); +} + +/// @brief Tests checkQueueStatus() logic for stopping the queue on shutdown +/// This test manually sets shutdown flag and verifies that queue manager +/// stop is initiated. +TEST_F(D2ProcessTest, queueStopOnShutdown) { + ASSERT_TRUE(runWithConfig(valid_d2_config)); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + setShutdownFlag(true); + + // Calling checkQueueStatus restart queue manager + checkQueueStatus(); + + // Verify that the queue manager is stopping. + EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Verify that a subsequent call with no events occurring in between, + // results in no change to queue manager + checkQueueStatus(); + + // Verify that the queue manager is still stopping. + EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Call runIO so the IO cancel event occurs and verify that queue manager + // has stopped. + runIO(); + ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState()); +} + +/// @brief Tests checkQueueStatus() logic for stopping the queue on reconfigure. +/// This test manually sets queue reconfiguration flag and verifies that queue +/// manager stop is initiated. +TEST_F(D2ProcessTest, queueStopOnReconf) { + ASSERT_TRUE(runWithConfig(valid_d2_config)); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + // Manually set the reconfigure indicator. + setReconfQueueFlag(true); + + // Calling checkQueueStatus should initiate stopping the queue manager. + checkQueueStatus(); + + // Verify that the queue manager is stopping. + EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Call runIO so the IO cancel event occurs and verify that queue manager + // has stopped. + runIO(); + ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState()); +} + +/// @brief Tests checkQueueStatus() logic for recovering from queue full +/// This test manually creates a receive queue full condition and then +/// "drains" the queue until the queue manager resumes listening. This +/// verifies D2Process's ability to recover from a queue full condition. +TEST_F(D2ProcessTest, queueFullRecovery) { + // Valid test message, contents are unimportant. + const char* test_msg = + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " + "}"; + + // Start queue manager with known good config. + ASSERT_TRUE(runWithConfig(valid_d2_config)); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + // Set the maximum queue size to manageable number. + size_t max_queue_size = 5; + queue_mgr->setMaxQueueSize(max_queue_size); + + // Manually enqueue max requests. + dhcp_ddns::NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg)); + for (int i = 0; i < max_queue_size; i++) { + // Verify that the request can be added to the queue and queue + // size increments accordingly. + ASSERT_NO_THROW(queue_mgr->enqueue(ncr)); + ASSERT_EQ(i+1, queue_mgr->getQueueSize()); + } + + // Since we are not really receiving, we will simulate QUEUE FULL + // detection. + queue_mgr->stopListening(D2QueueMgr::STOPPED_QUEUE_FULL); + ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Call runIO so the IO cancel event occurs and verify that queue manager + // has stopped. + runIO(); + ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState()); + + // Dequeue requests one at a time, calling checkQueueStatus after each + // dequeue, until we reach the resume threshold. This simulates update + // manager consuming jobs. Queue manager should remain stopped during + // this loop. + int resume_threshold = (max_queue_size * QUEUE_RESTART_PERCENT); + while (queue_mgr->getQueueSize() > resume_threshold) { + checkQueueStatus(); + ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState()); + ASSERT_NO_THROW(queue_mgr->dequeue()); + } + + // Dequeue one more, which brings us under the threshold and call + // checkQueueStatus. + // Verify that the queue manager is again running. + ASSERT_NO_THROW(queue_mgr->dequeue()); + checkQueueStatus(); + EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); +} + +/// @brief Tests checkQueueStatus() logic for queue receive error recovery +/// This test manually creates a queue receive error condition and tests +/// verifies that checkQueueStatus reacts properly to recover. +TEST_F(D2ProcessTest, queueErrorRecovery) { + ASSERT_TRUE(runWithConfig(valid_d2_config)); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + // Since we are not really receiving, we have to stage an error. + queue_mgr->stopListening(D2QueueMgr::STOPPED_RECV_ERROR); + ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Call runIO so the IO cancel event occurs and verify that queue manager + // has stopped. + runIO(); + ASSERT_EQ(D2QueueMgr::STOPPED_RECV_ERROR, queue_mgr->getMgrState()); + + // Calling checkQueueStatus should restart queue manager + checkQueueStatus(); + + // Verify that queue manager is again running. + EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); +} + +/// @brief Verifies queue manager recovery from unusable configuration +/// This test checks D2Process's gracefully handle a configuration which +/// while valid is not operationally usable (i.e. IP address is unavailable), +/// and to subsequently recover given a usable configuration. +TEST_F(D2ProcessTest, badConfigureRecovery) { + D2QueueMgrPtr queue_mgr = getD2QueueMgr(); + ASSERT_TRUE(queue_mgr); + + // Verify the queue manager is not initialized. + EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState()); + + // Invoke configure() with a valid config that contains an unusable IP + ASSERT_TRUE(fromJSON(bad_ip_d2_config)); + ConstElementPtr answer = configure(config_set_, false); + + // Verify that configure result is success and reconfigure queue manager + // flag is true. + ASSERT_TRUE(checkAnswer(answer, 0)); + ASSERT_TRUE(getReconfQueueFlag()); + + // Call checkQueueStatus to cause queue manager to attempt to reconfigure. + checkQueueStatus(); + + // Verify that queue manager failed to start, (i.e. is in INITTED state), + // and the reconfigure flag is false. + ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr->getMgrState()); + ASSERT_FALSE(getReconfQueueFlag()); + + // Verify we can recover given a valid config with an usable IP address. + ASSERT_TRUE(fromJSON(valid_d2_config)); + answer = configure(config_set_, false); + + // Verify that configure result is success and reconfigure queue manager + // flag is true. + ASSERT_TRUE(checkAnswer(answer, 0)); + ASSERT_TRUE(getReconfQueueFlag()); + + // Call checkQueueStatus to cause queue manager to reconfigure and start. + checkQueueStatus(); + + // Verify that queue manager is now in the RUNNING state, and reconfigure + // flag is false. + EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState()); + EXPECT_FALSE(getReconfQueueFlag()); +} + +/// @brief Tests shutdown command argument parsing +/// The shutdown command supports an optional "type" argument. This test +/// checks that for valid values, the shutdown() method: sets the shutdown +/// type to correct value, set the shutdown flag to true, and returns a +/// success response; and for invalid values: sets the shutdown flag to false +/// and returns a failure response. +TEST_F(D2ProcessTest, shutdownArgs) { + ElementPtr args; + ConstElementPtr answer; + const char* default_args = "{}"; + const char* normal_args = "{ \"type\" : \"normal\" }"; + const char* drain_args = "{ \"type\" : \"drain_first\" }"; + const char* now_args = "{ \"type\" : \"now\" }"; + const char* bogus_args = "{ \"type\" : \"bogus\" }"; + + // Verify defaulting to SD_NORMAL if no argument is given. + ASSERT_NO_THROW(args = Element::fromJSON(default_args)); + EXPECT_NO_THROW(answer = shutdown(args)); + ASSERT_TRUE(checkAnswer(answer, 0)); + EXPECT_EQ(SD_NORMAL, getShutdownType()); + EXPECT_TRUE(shouldShutdown()); + + // Verify argument value "normal". + ASSERT_NO_THROW(args = Element::fromJSON(normal_args)); + EXPECT_NO_THROW(answer = shutdown(args)); + ASSERT_TRUE(checkAnswer(answer, 0)); + EXPECT_EQ(SD_NORMAL, getShutdownType()); + EXPECT_TRUE(shouldShutdown()); + + // Verify argument value "drain_first". + ASSERT_NO_THROW(args = Element::fromJSON(drain_args)); + EXPECT_NO_THROW(answer = shutdown(args)); + ASSERT_TRUE(checkAnswer(answer, 0)); + EXPECT_EQ(SD_DRAIN_FIRST, getShutdownType()); + EXPECT_TRUE(shouldShutdown()); + + // Verify argument value "now". + ASSERT_NO_THROW(args = Element::fromJSON(now_args)); + EXPECT_NO_THROW(answer = shutdown(args)); + ASSERT_TRUE(checkAnswer(answer, 0)); + EXPECT_EQ(SD_NOW, getShutdownType()); + EXPECT_TRUE(shouldShutdown()); + + // Verify correct handling of an invalid value. + ASSERT_NO_THROW(args = Element::fromJSON(bogus_args)); + EXPECT_NO_THROW(answer = shutdown(args)); + ASSERT_TRUE(checkAnswer(answer, 1)); + EXPECT_FALSE(shouldShutdown()); +} + +/// @brief Tests shutdown criteria logic +/// D2Process using the method canShutdown() to determine if a shutdown +/// can be performed given the value of the shutdown flag and the type of +/// shutdown requested. For each shutdown type certain criteria must be met +/// before the shutdown is permitted. This method is invoked once each pass +/// through the main event loop. This test checks the operation of the +/// canShutdown method. It uses a convenience method, checkCanShutdown(), +/// which sets the shutdown type to the given value and invokes canShutdown(), +/// returning its result. +TEST_F(D2ProcessTest, canShutdown) { + ASSERT_TRUE(runWithConfig(valid_d2_config)); + const D2QueueMgrPtr& queue_mgr = getD2QueueMgr(); + + // Shutdown flag is false. Method should return false for all types. + EXPECT_FALSE(checkCanShutdown(SD_NORMAL)); + EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_FALSE(checkCanShutdown(SD_NOW)); + + // Set shutdown flag to true. + setShutdownFlag(true); + + // Queue Manager is running, queue is empty, no transactions. + // Only SD_NOW should return true. + EXPECT_FALSE(checkCanShutdown(SD_NORMAL)); + EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_TRUE(checkCanShutdown(SD_NOW)); + + // Tell queue manager to stop. + queue_mgr->stopListening(); + // Verify that the queue manager is stopping. + EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState()); + + // Queue Manager is stopping, queue is empty, no transactions. + // Only SD_NOW should return true. + EXPECT_FALSE(checkCanShutdown(SD_NORMAL)); + EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_TRUE(checkCanShutdown(SD_NOW)); + + // Allow cancel event to process. + ASSERT_NO_THROW(runIO()); + // Verify that queue manager is stopped. + EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState()); + + // Queue Manager is stopped, queue is empty, no transactions. + // All types should return true. + EXPECT_TRUE(checkCanShutdown(SD_NORMAL)); + EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_TRUE(checkCanShutdown(SD_NOW)); + + const char* test_msg = + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"fish.example.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " + "}"; + + // Manually enqueue a request. This lets us test logic with queue + // not empty. + dhcp_ddns::NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg)); + ASSERT_NO_THROW(queue_mgr->enqueue(ncr)); + ASSERT_EQ(1, queue_mgr->getQueueSize()); + + // Queue Manager is stopped. Queue is not empty, no transactions. + // SD_DRAIN_FIRST should be false, SD_NORMAL and SD_NOW should be true. + EXPECT_TRUE(checkCanShutdown(SD_NORMAL)); + EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_TRUE(checkCanShutdown(SD_NOW)); + + // Now use update manager to dequeue the request and make a transaction. + // This lets us verify transaction list not empty logic. + const D2UpdateMgrPtr& update_mgr = getD2UpdateMgr(); + ASSERT_TRUE(update_mgr); + ASSERT_NO_THROW(update_mgr->sweep()); + ASSERT_EQ(0, queue_mgr->getQueueSize()); + ASSERT_EQ(1, update_mgr->getTransactionCount()); + + // Queue Manager is stopped. Queue is empty, one transaction. + // Only SD_NOW should be true. + EXPECT_FALSE(checkCanShutdown(SD_NORMAL)); + EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST)); + EXPECT_TRUE(checkCanShutdown(SD_NOW)); +} + +/// @brief Verifies that an "external" call to shutdown causes the run method +/// to exit gracefully. +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(*getIoService()); + timer.setup(std::bind(&D2ProcessTest::genShutdownCallback, this), + 2 * 1000); + + // Record start time, and invoke run(). + ptime start = microsec_clock::universal_time(); + EXPECT_NO_THROW(run()); + + // Record stop time. + ptime stop = microsec_clock::universal_time(); + + // 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() <= 2200); +} + +/// @brief Verifies that an "uncaught" exception thrown during event loop +/// execution 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(*getIoService()); + timer.setup(std::bind(&D2ProcessTest::genFatalErrorCallback, this), + 2 * 1000); + + // Record start time, and invoke run(). + ptime start = microsec_clock::universal_time(); + EXPECT_THROW(run(), DProcessBaseError); + + // Record stop time. + ptime stop = microsec_clock::universal_time(); + + // 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() <= 2200); +} + +/// @brief Used to permit visual inspection of logs to ensure +/// DHCP_DDNS_NOT_ON_LOOPBACK is issued when ip_address is not +/// loopback. +TEST_F(D2ProcessTest, notLoopbackTest) { + const char* config = "{ " + "\"ip-address\" : \"0.0.0.0\" , " + "\"port\" : 53001, " + "\"tsig-keys\": []," + "\"forward-ddns\" : {}," + "\"reverse-ddns\" : {}" + "}"; + + // Note we don't care nor can we predict if this + // succeeds or fails. The address and port may or may + // not be valid on the test host. + runWithConfig(config); +} + +/// @brief Used to permit visual inspection of logs to ensure +/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued. +TEST_F(D2ProcessTest, v4LoopbackTest) { + const char* config = "{ " + "\"ip-address\" : \"127.0.0.1\" , " + "\"port\" : 53001, " + "\"tsig-keys\": []," + "\"forward-ddns\" : {}," + "\"reverse-ddns\" : {}" + "}"; + ASSERT_TRUE(runWithConfig(config)); +} + +/// @brief Used to permit visual inspection of logs to ensure +/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued. +TEST_F(D2ProcessTest, v6LoopbackTest) { + const char* config = "{ " + "\"ip-address\" : \"::1\" , " + "\"port\" : 53001, " + "\"tsig-keys\": []," + "\"forward-ddns\" : {}," + "\"reverse-ddns\" : {}" + "}"; + ASSERT_TRUE(runWithConfig(config)); +} + +/// @brief Check the configured callout (positive case). +TEST_F(D2ProcessTest, configuredNoFail) { + const char* config = "{\n" + "\"hooks-libraries\": [ {\n" + " \"library\": \"%LIBRARY%\",\n" + " \"parameters\": {\n" + " } } ] }\n"; + string cfg = pathReplacer(config, CONFIGURED_LIBRARY); + + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(cfg)); + ConstElementPtr answer; + ASSERT_NO_THROW(answer = configure(json, false)); + int rcode = -1; + ConstElementPtr comment; + comment = isc::config::parseAnswer(rcode, answer); + EXPECT_EQ(0, rcode) << comment->str(); +} + +/// @brief Check the configured callout (negative case). +TEST_F(D2ProcessTest, configuredFail) { + const char* config = "{\n" + "\"user-context\": { \"error\": \"Fail!\" },\n" + "\"hooks-libraries\": [ {\n" + " \"library\": \"%LIBRARY%\",\n" + " \"parameters\": {\n" + " } } ] }\n"; + string cfg = pathReplacer(config, CONFIGURED_LIBRARY); + + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(cfg)); + ConstElementPtr answer; + ASSERT_NO_THROW(answer = configure(json, false)); + int rcode = -1; + ConstElementPtr comment; + comment = isc::config::parseAnswer(rcode, answer); + EXPECT_EQ(1, rcode); + ASSERT_TRUE(comment); + ASSERT_EQ(Element::string, comment->getType()); + EXPECT_EQ("Fail!", comment->stringValue()); +} + +} // end of anonymous namespace |