d2_process_unittests.cc 24.9 KB
Newer Older
1
// Copyright (C) 2013-2016 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

15
#include <boost/bind.hpp>
16 17 18 19 20 21 22 23 24
#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>

#include <sstream>

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

namespace {

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

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

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

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

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

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

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
    /// @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;
        }

        isc::data::ConstElementPtr answer = configure(config_set_);
        isc::data::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();
110
        const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
111

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

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

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

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

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

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

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

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

204 205 206 207 208 209 210 211 212 213 214 215
    // Verify that configure result is failure, the reconfigure flag is
    // false, and that the queue manager is still running.
    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));
216
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

    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));
244
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

    // 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 =
        "{"
270 271 272
        " \"change-type\" : 0 , "
        " \"forward-change\" : true , "
        " \"reverse-change\" : false , "
273
        " \"fqdn\" : \"walah.walah.com\" , "
274
        " \"ip-address\" : \"192.168.2.1\" , "
275
        " \"dhcid\" : \"010203040A7F8E3D\" , "
276 277
        " \"lease-expires-on\" : \"20130121132405\" , "
        " \"lease-length\" : 1300 "
278 279 280 281
        "}";

    // Start queue manager with known good config.
    ASSERT_TRUE(runWithConfig(valid_d2_config));
282
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
283 284 285 286 287 288 289 290 291 292 293 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

    // 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));
332
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
333 334 335 336 337 338 339 340 341 342 343 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 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393

    // 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));
    isc::data::ConstElementPtr answer = configure(config_set_);

    // 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));
    answer = configure(config_set_);

    // 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());
394 395
}

396 397 398
/// @brief Verifies basic command method behavior.
/// @TODO IF the D2Process is extended to support extra commands this testing
/// will need to augmented accordingly.
399
TEST_F(D2ProcessTest, command) {
400
    // Verify that the process will process unsupported command and
401 402 403 404
    // return a failure response.
    int rcode = -1;
    string args = "{ \"arg1\": 77 } ";
    isc::data::ElementPtr json = isc::data::Element::fromJSON(args);
405
    isc::data::ConstElementPtr answer = command("bogus_command", json);
406
    parseAnswer(rcode, answer);
407
    EXPECT_EQ(COMMAND_INVALID, rcode);
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 468 469 470
/// @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));
471
    const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
472 473

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

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

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

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

    // 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.
505 506 507
    EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
    EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST));
    EXPECT_TRUE(checkCanShutdown(SD_NOW));
508 509 510

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

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

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


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

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

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

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

574

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

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

    // Record stop time.
589
    ptime stop = microsec_clock::universal_time();
590 591 592

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

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

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

644
} // end of anonymous namespace