d2_process_unittests.cc 24.9 KB
Newer Older
1
// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6

7
#include <config.h>
8

9
#include <asiolink/io_service.h>
10
#include <cc/command_interpreter.h>
11
#include <d2/d2_process.h>
12
#include <dhcp_ddns/ncr_io.h>
13
#include <process/testutils/d_test_stubs.h>
14
#include <d2/tests/nc_test_utils.h>
15 16 17 18

#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>

19
#include <functional>
20 21 22 23 24 25
#include <sstream>

using namespace std;
using namespace isc;
using namespace isc::config;
using namespace isc::d2;
26
using namespace isc::process;
27 28 29 30
using namespace boost::posix_time;

namespace {

31 32
/// @brief Valid configuration containing an unavailable IP address.
const char* bad_ip_d2_config = "{ "
33
                        "\"ip-address\" : \"1.1.1.1\" , "
34
                        "\"port\" : 5031, "
35
                        "\"tsig-keys\": ["
36
                        "{ \"name\": \"d2_key.example.com\" , "
37
                        "   \"algorithm\": \"HMAC-MD5\" ,"
38
                        "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
39
                        "} ],"
40 41
                        "\"forward-ddns\" : {"
                        "\"ddns-domains\": [ "
42 43
                        "{ \"name\": \"example.com\" , "
                        "  \"key-name\": \"d2_key.example.com\" , "
44 45
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.101\" } "
46
                        "] } ] }, "
47 48
                        "\"reverse-ddns\" : {"
                        "\"ddns-domains\": [ "
49
                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
50
                        "  \"key-name\": \"d2_key.example.com\" , "
51 52
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.101\" , "
53 54 55
                        "    \"port\": 100 } ] } "
                        "] } }";

56
/// @brief D2Process test fixture class
57 58
//class D2ProcessTest : public D2Process, public ::testing::Test {
class D2ProcessTest : public D2Process, public ConfigParseTest {
59 60 61
public:

    /// @brief Constructor
62 63 64
    D2ProcessTest() :
        D2Process("d2test",
                  asiolink::IOServicePtr(new isc::asiolink::IOService())) {
65 66
    }

67
    /// @brief Destructor
68
    virtual ~D2ProcessTest() {
69 70 71
    }

    /// @brief Callback that will invoke shutdown method.
72 73
    void genShutdownCallback() {
        shutdown(isc::data::ConstElementPtr());
74 75 76
    }

    /// @brief Callback that throws an exception.
77
    void genFatalErrorCallback() {
78
        isc_throw (DProcessBaseError, "simulated fatal error");
79 80
    }

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    /// @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;
        }

98
        isc::data::ConstElementPtr answer = configure(config_set_, false);
99 100 101 102 103
        isc::data::ConstElementPtr comment;
        comment = isc::config::parseAnswer(rcode, answer);

        if (rcode) {
            return (::testing::AssertionFailure(::testing::Message() <<
104
                                                "configure() failed: "
105 106 107 108 109 110
                                                << comment));
        }

        // Must call checkQueueStatus, to cause queue manager to reconfigure
        // and start.
        checkQueueStatus();
111
        const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
112

113 114 115 116 117
        // 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"));
        }
118

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
        //  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());
    }
};
134

135
/// @brief Verifies D2Process construction behavior.
136 137
/// 1. Verifies that constructor fails with an invalid IOService
/// 2. Verifies that constructor succeeds with a valid IOService
138
/// 3. Verifies that all managers are accessible
139 140 141
TEST(D2Process, construction) {
    // Verify that the constructor will fail if given an empty
    // io service.
142
    asiolink::IOServicePtr lcl_io_service;
143
    EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
144 145 146 147

    // 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));
148 149 150 151 152 153 154 155 156 157 158

    // 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);

159
    const D2UpdateMgrPtr& update_mgr = d2process.getD2UpdateMgr();
160
    ASSERT_TRUE(update_mgr);
161 162 163
}

/// @brief Verifies basic configure method behavior.
164 165 166 167 168 169 170 171
/// 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.
172
TEST_F(D2ProcessTest, configure) {
173 174 175 176 177 178 179 180 181 182 183 184
    // 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.
185
    isc::data::ConstElementPtr answer = configure(config_set_, false);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

    // 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.
203
    answer = configure(config_set_, false);
204

205 206 207 208 209 210 211 212 213 214 215
    // 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);
216
    ASSERT_TRUE(checkAnswer(answer, 1));
217 218 219 220 221 222 223 224 225
    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));
226
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

    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));
254
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

    // 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 =
        "{"
280 281 282
        " \"change-type\" : 0 , "
        " \"forward-change\" : true , "
        " \"reverse-change\" : false , "
283
        " \"fqdn\" : \"walah.walah.com\" , "
284
        " \"ip-address\" : \"192.168.2.1\" , "
285
        " \"dhcid\" : \"010203040A7F8E3D\" , "
286
        " \"lease-expires-on\" : \"20130121132405\" , "
287 288
        " \"lease-length\" : 1300, "
        " \"use-conflict-resolution\" : true "
289 290 291 292
        "}";

    // Start queue manager with known good config.
    ASSERT_TRUE(runWithConfig(valid_d2_config));
293
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

    // 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));
343
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373

    // 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));
374
    isc::data::ConstElementPtr answer = configure(config_set_, false);
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

    // 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 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));
391
    answer = configure(config_set_, false);
392 393 394 395 396 397 398 399 400 401 402 403 404

    // 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());
405 406
}

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
/// @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) {
    isc::data::ElementPtr args;
    isc::data::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 = isc::data::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 = isc::data::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 = isc::data::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 = isc::data::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 = isc::data::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));
468
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
469 470

    // Shutdown flag is false.  Method should return false for all types.
471 472 473
    EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
    EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_FALSE(checkCanShutdown(SD_NOW));
474 475 476 477 478 479

    // Set shutdown flag to true.
    setShutdownFlag(true);

    // Queue Manager is running, queue is empty, no transactions.
    // Only SD_NOW should return true.
480 481 482
    EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
    EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
483 484 485 486 487 488 489 490

    // 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.
491 492 493
    EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
    EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
494 495 496 497 498 499 500 501

    // 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.
502 503 504
    EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
    EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
505 506 507

    const char* test_msg =
        "{"
508 509 510
        " \"change-type\" : 0 , "
        " \"forward-change\" : true , "
        " \"reverse-change\" : false , "
511
        " \"fqdn\" : \"fish.example.com\" , "
512
        " \"ip-address\" : \"192.168.2.1\" , "
513
        " \"dhcid\" : \"010203040A7F8E3D\" , "
514
        " \"lease-expires-on\" : \"20130121132405\" , "
515 516
        " \"lease-length\" : 1300, "
        " \"use-conflict-resolution\" : true "
517 518 519 520 521 522 523 524 525 526 527
        "}";

    // 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.
528 529 530
    EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
    EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
531 532 533

    // Now use update manager to dequeue the request and make a transaction.
    // This lets us verify transaction list not empty logic.
534
    const D2UpdateMgrPtr& update_mgr = getD2UpdateMgr();
535 536 537 538 539 540 541
    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.
542 543 544
    EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
    EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
545 546 547
}


548 549
/// @brief Verifies that an "external" call to shutdown causes the run method
/// to exit gracefully.
550 551
TEST_F(D2ProcessTest, normalShutdown) {
    // Use an asiolink IntervalTimer and callback to generate the
552
    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
553
    isc::asiolink::IntervalTimer timer(*getIoService());
554
    timer.setup(std::bind(&D2ProcessTest::genShutdownCallback, this),
555
                2 * 1000);
556 557

    // Record start time, and invoke run().
558
    ptime start = microsec_clock::universal_time();
559
    EXPECT_NO_THROW(run());
560 561

    // Record stop time.
562
    ptime stop = microsec_clock::universal_time();
563 564 565 566 567

    // 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;
568
    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
569
                elapsed.total_milliseconds() <= 2200);
570 571
}

572

573 574
/// @brief Verifies that an "uncaught" exception thrown during event loop
/// execution is treated as a fatal error.
575 576
TEST_F(D2ProcessTest, fatalErrorShutdown) {
    // Use an asiolink IntervalTimer and callback to generate the
577
    // the exception.  (Note IntervalTimer setup is in milliseconds).
578
    isc::asiolink::IntervalTimer timer(*getIoService());
579
    timer.setup(std::bind(&D2ProcessTest::genFatalErrorCallback, this),
580
                2 * 1000);
581 582

    // Record start time, and invoke run().
583
    ptime start = microsec_clock::universal_time();
584
    EXPECT_THROW(run(), DProcessBaseError);
585 586

    // Record stop time.
587
    ptime stop = microsec_clock::universal_time();
588 589 590

    // Verify that duration of the run invocation is the same as the
    // timer duration.  This demonstrates that the anomaly occurred
591
    // during io callback processing.
592
    time_duration elapsed = stop - start;
593
    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
594
                elapsed.total_milliseconds() <= 2200);
595 596
}

597 598 599 600 601
/// @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 = "{ "
602
                        "\"ip-address\" : \"0.0.0.0\" , "
603
                        "\"port\" : 53001, "
604 605 606
                        "\"tsig-keys\": [],"
                        "\"forward-ddns\" : {},"
                        "\"reverse-ddns\" : {}"
607 608 609 610 611 612 613 614 615 616 617 618 619
                        "}";

    // 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 = "{ "
620
                        "\"ip-address\" : \"127.0.0.1\" , "
621
                        "\"port\" : 53001, "
622 623 624
                        "\"tsig-keys\": [],"
                        "\"forward-ddns\" : {},"
                        "\"reverse-ddns\" : {}"
625 626 627 628 629 630 631 632
                        "}";
    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 = "{ "
633
                        "\"ip-address\" : \"::1\" , "
634
                        "\"port\" : 53001, "
635 636 637
                        "\"tsig-keys\": [],"
                        "\"forward-ddns\" : {},"
                        "\"reverse-ddns\" : {}"
638 639 640 641
                        "}";
    ASSERT_TRUE(runWithConfig(config));
}

642
} // end of anonymous namespace