d_controller_unittests.cc 18.5 KB
Newer Older
1
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
//
// 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 <d_test_stubs.h>
#include <d2/spec_config.h>

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

#include <config.h>
#include <sstream>

using namespace boost::posix_time;

namespace isc {
namespace d2 {

30 31 32 33 34 35 36 37 38
/// @brief Test fixture class for testing DControllerBase class. This class
/// derives from DControllerTest and wraps a DStubController.  DStubController
/// has been constructed to exercise DControllerBase.
class DStubControllerTest : public DControllerTest {
public:
    /// @brief Constructor.
    /// Note the constructor passes in the static DStubController instance
    /// method.
    DStubControllerTest() : DControllerTest (DStubController::instance) {
39 40 41
        controller_ = boost::dynamic_pointer_cast<DStubController>
                                                 (DControllerTest::
                                                  getController());
42 43 44 45
    }

    virtual ~DStubControllerTest() {
    }
46

47
    DStubControllerPtr controller_;
48 49 50 51 52
};

/// @brief Basic Controller instantiation testing.
/// Verfies that the controller singleton gets created and that the
/// basic derivation from the base class is intact.
53
TEST_F(DStubControllerTest, basicInstanceTesting) {
54
    // Verify that the singleton exists and it is the correct type.
55 56
    DControllerBasePtr& controller = DControllerTest::getController();
    ASSERT_TRUE(controller);
57 58
    ASSERT_NO_THROW(boost::dynamic_pointer_cast<DStubController>(controller));

59 60 61 62 63
    // Verify that controller's app name is correct.
    EXPECT_TRUE(checkAppName(DStubController::stub_app_name_));

    // Verify that controller's bin name is correct.
    EXPECT_TRUE(checkBinName(DStubController::stub_bin_name_));
64 65

    // Verify that controller's spec file name is correct.
66
    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
67 68

    // Verify that controller's IOService exists.
69 70
    EXPECT_TRUE(checkIOService());

71
    // Verify that the Process does NOT exist.
72 73 74
    EXPECT_FALSE(checkProcess());
}

75 76 77 78 79 80
/// @brief Tests basic command line processing.
/// Verifies that:
/// 1. Standard command line options are supported.
/// 2. Custom command line options are supported.
/// 3. Invalid options are detected.
/// 4. Extraneous command line information is detected.
81 82
TEST_F(DStubControllerTest, commandLineArgs) {

83
    // Verify that verbose flag is false initially.
84 85
    EXPECT_TRUE(checkVerbose(false));

86
    // Verify that standard options can be parsed without error.
87
    char* argv[] = { const_cast<char*>("progName"),
88 89
                     const_cast<char*>("-c"),
                     const_cast<char*>("cfgName"),
90
                     const_cast<char*>("-v") };
91
    int argc = 4;
92 93
    EXPECT_NO_THROW(parseArgs(argc, argv));

94
    // Verify that verbose is true.
95 96
    EXPECT_TRUE(checkVerbose(true));

97 98 99
    // Verify configuration file name is correct
    EXPECT_TRUE(checkConfigFileName("cfgName"));

100
    // Verify that the custom command line option is parsed without error.
101 102 103
    char xopt[3] = "- ";
    xopt[1] =  *DStubController::stub_option_x_;
    char* argv1[] = { const_cast<char*>("progName"), xopt};
104 105 106
    argc = 2;
    EXPECT_NO_THROW (parseArgs(argc, argv1));

107
    // Verify that an unknown option is detected.
108 109
    char* argv2[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-bs") };
110 111
    argc = 2;
    EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
112 113

    // Verify that extraneous information is detected.
114 115 116
    char* argv3[] = { const_cast<char*>("progName"),
                      const_cast<char*>("extra"),
                      const_cast<char*>("information") };
117 118
    argc = 3;
    EXPECT_THROW (parseArgs(argc, argv3), InvalidUsage);
119 120
}

121 122 123 124 125 126
/// @brief Tests application process creation and initialization.
/// Verifies that:
/// 1. An error during process creation is handled.
/// 2. A NULL returned by process creation is handled.
/// 3. An error during process initialization is handled.
/// 4. Process can be successfully created and initialized.
127
TEST_F(DStubControllerTest, initProcessTesting) {
128
    // Verify that a failure during process creation is caught.
129 130 131 132
    SimFailure::set(SimFailure::ftCreateProcessException);
    EXPECT_THROW(initProcess(), DControllerBaseError);
    EXPECT_FALSE(checkProcess());

133
    // Verify that a NULL returned by process creation is handled.
134 135 136 137
    SimFailure::set(SimFailure::ftCreateProcessNull);
    EXPECT_THROW(initProcess(), DControllerBaseError);
    EXPECT_FALSE(checkProcess());

138
    // Re-create controller, verify that we are starting clean
139
    resetController();
140 141 142
    EXPECT_FALSE(checkProcess());

    // Verify that an error during process initialization is handled.
143 144 145
    SimFailure::set(SimFailure::ftProcessInit);
    EXPECT_THROW(initProcess(), DProcessBaseError);

146
    // Re-create controller, verify that we are starting clean
147 148 149
    resetController();
    EXPECT_FALSE(checkProcess());

150
    // Verify that the application process can created and initialized.
151 152 153 154
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());
}

155
/// @brief Tests launch handling of invalid command line.
156 157
/// This test launches with an invalid command line which should throw
/// an InvalidUsage.
158 159
TEST_F(DStubControllerTest, launchInvalidUsage) {
    // Command line to run integrated
160 161
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-z") };
162 163 164
    int argc = 2;

    // Launch the controller in integrated mode.
165
    EXPECT_THROW(launch(argc, argv), InvalidUsage);
166 167 168 169
}

/// @brief Tests launch handling of failure in application process
/// initialization.  This test launches with a valid command line but with
170 171
/// SimFailure set to fail during process creation.  Launch should throw
/// ProcessInitError.
172 173
TEST_F(DStubControllerTest, launchProcessInitError) {
    // Command line to run integrated
174
    char* argv[] = { const_cast<char*>("progName"),
175 176
                     const_cast<char*>("-c"),
                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
177
                     const_cast<char*>("-v") };
178
    int argc = 4;
179 180 181

    // Launch the controller in stand alone mode.
    SimFailure::set(SimFailure::ftCreateProcessException);
182
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
183 184
}

185 186
/// @brief Tests launch and normal shutdown (stand alone mode).
/// This creates an interval timer to generate a normal shutdown and then
187 188
/// launches with a valid, command line, with a valid configuration file
///  and no simulated errors.
189
TEST_F(DStubControllerTest, launchNormalShutdown) {
190 191 192
    // Write the valid, empty, config and then run launch() for 1000 ms
    time_duration elapsed_time;
    ASSERT_NO_THROW(runWithConfig("{}", 2000, elapsed_time));
193 194 195 196

    // 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.
197 198
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
199 200
}

201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
/// @brief Tests launch with an nonexistant configuration file.
TEST_F(DStubControllerTest, nonexistantConfigFile) {
    // command line to run standalone
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-c"),
                     const_cast<char*>("bogus-file"),
                     const_cast<char*>("-v") };
    int argc = 4;

    // Record start time, and invoke launch().
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
}

/// @brief Tests launch with configuration file argument but no file name
TEST_F(DStubControllerTest, missingConfigFileName) {
    // command line to run standalone
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-c"),
                     const_cast<char*>("-v") };
    int argc = 3;

    // Record start time, and invoke launch().
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
}

/// @brief Tests launch with no configuration file argument
TEST_F(DStubControllerTest, missingConfigFileArgument) {
    // command line to run standalone
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-v") };
    int argc = 2;

    // Record start time, and invoke launch().
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
}

237 238 239
/// @brief Tests launch with an operational error during application execution.
/// This test creates an interval timer to generate a runtime exception during
/// the process event loop. It launches wih a valid, stand-alone command line
240
/// and no simulated errors.  Launch should throw ProcessRunError.
241 242 243 244
TEST_F(DStubControllerTest, launchRuntimeError) {
    // Use an asiolink IntervalTimer and callback to generate the
    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
    isc::asiolink::IntervalTimer timer(*getIOService());
245
    timer.setup(genFatalErrorCallback, 2000);
246

247 248 249
    // Write the valid, empty, config and then run launch() for 1000 ms
    time_duration elapsed_time;
    EXPECT_THROW(runWithConfig("{}", 2000, elapsed_time), ProcessRunError);
250 251 252 253

    // 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.
254 255
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
256 257
}

258
/// @brief Configuration update event testing.
259
/// This really tests just the ability of the handlers to invoke the necessary
260 261
/// chain of methods and handle error conditions. Configuration parsing and
/// retrieval should be tested as part of the d2 configuration management
262
/// implementation.
263
/// This test verifies that:
264 265
/// 1. That a valid configuration update results in successful status return.
/// 2. That an application process error in configuration updating is handled
266
/// properly.
267 268 269 270
TEST_F(DStubControllerTest, configUpdateTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;

271
    // Initialize the application process.
272 273 274
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

275
    // Create a configuration set. Content is arbitrary, just needs to be
276 277 278 279
    // valid JSON.
    std::string config = "{ \"test-value\": 1000 } ";
    isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);

280
    // Verify that a valid config gets a successful update result.
281
    answer = updateConfig(config_set);
282 283 284 285 286
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);

    // Verify that an error in process configure method is handled.
    SimFailure::set(SimFailure::ftProcessConfigure);
287
    answer = updateConfig(config_set);
288 289 290 291
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);
}

292 293
/// @brief Command execution tests.
/// This really tests just the ability of the handler to invoke the necessary
294
/// chain of methods and to handle error conditions.
295 296 297 298 299 300 301 302 303 304
/// This test verifies that:
/// 1. That an unrecognized command is detected and returns a status of
/// d2::COMMAND_INVALID.
/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
/// 3. A valid, custom controller command is recognized a d2::COMMAND_SUCCESS
/// status.
/// 4. A valid, custom process command is recognized a d2::COMMAND_SUCCESS
/// status.
/// 5. That a valid controller command that fails returns a d2::COMMAND_ERROR.
/// 6. That a valid process command that fails returns a d2::COMMAND_ERROR.
305 306 307 308 309
TEST_F(DStubControllerTest, executeCommandTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;
    isc::data::ElementPtr arg_set;

310
    // Initialize the application process.
311 312 313
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

314 315
    // Verify that an unknown command returns an d2::COMMAND_INVALID response.
    std::string bogus_command("bogus");
316
    answer = executeCommand(bogus_command, arg_set);
317 318 319 320
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_INVALID, rcode);

    // Verify that shutdown command returns d2::COMMAND_SUCCESS response.
321
    answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
322 323 324
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);

325 326
    // Verify that a valid custom controller command returns
    // d2::COMMAND_SUCCESS response.
327
    answer = executeCommand(DStubController::stub_ctl_command_, arg_set);
328 329 330
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);

331
    // Verify that a valid custom process command returns d2::COMMAND_SUCCESS
332
    // response.
333
    answer = executeCommand(DStubProcess::stub_proc_command_, arg_set);
334 335
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);
336 337 338 339

    // Verify that a valid custom controller command that fails returns
    // a d2::COMMAND_ERROR.
    SimFailure::set(SimFailure::ftControllerCommand);
340
    answer = executeCommand(DStubController::stub_ctl_command_, arg_set);
341 342 343 344 345 346
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_ERROR, rcode);

    // Verify that a valid custom process command that fails returns
    // a d2::COMMAND_ERROR.
    SimFailure::set(SimFailure::ftProcessCommand);
347
    answer = executeCommand(DStubProcess::stub_proc_command_, arg_set);
348 349
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_ERROR, rcode);
350 351
}

352 353 354 355 356 357
// Tests that registered signals are caught and handled.
TEST_F(DStubControllerTest, ioSignals) {
    // Tell test controller just to record the signals, don't call the
    // base class signal handler.
    controller_->recordSignalOnly(true);

358
    // Setup to raise SIGHUP in 10 ms.
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
    TimedSignal sighup(*getIOService(), SIGHUP, 10);
    TimedSignal sigint(*getIOService(), SIGINT, 10);
    TimedSignal sigterm(*getIOService(), SIGTERM, 10);

    // Write the valid, empty, config and then run launch() for 500 ms
    time_duration elapsed_time;
    runWithConfig("{}", 500, elapsed_time);

    // Verify that we caught the signals as expected.
    std::vector<int>& signals = controller_->getProcessedSignals();
    ASSERT_EQ(3, signals.size());
    EXPECT_EQ(SIGHUP, signals[0]);
    EXPECT_EQ(SIGINT, signals[1]);
    EXPECT_EQ(SIGTERM, signals[2]);
}

// Tests that the original configuration is retained after a SIGHUP triggered
// reconfiguration fails due to invalid config content.
TEST_F(DStubControllerTest, invalidConfigReload) {
    // Schedule to rewrite the configuration file after launch. This way the
    // file is updated after we have done the initial configuration.  The
    // new content is invalid JSON which will cause the config parse to fail.
    scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);

383
    // Setup to raise SIGHUP in 200 ms.
384 385 386
    TimedSignal sighup(*getIOService(), SIGHUP, 200);

    // Write the config and then run launch() for 500 ms
387 388
    // After startup, which will load the initial configuration this enters
    // the process's runIO() loop. We will first rewrite the config file.
389 390 391 392
    // Next we process the SIGHUP signal which should cause us to reconfigure.
    time_duration elapsed_time;
    runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);

393
    // Context is still available post launch. Check to see that our
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    // configuration value is still the original value.
    std::string  actual_value = "";
    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
    EXPECT_EQ("first value", actual_value);

    // Verify that we saw the signal.
    std::vector<int>& signals = controller_->getProcessedSignals();
    ASSERT_EQ(1, signals.size());
    EXPECT_EQ(SIGHUP, signals[0]);
}

// Tests that the original configuration is replaced after a SIGHUP triggered
// reconfiguration succeeds.
TEST_F(DStubControllerTest, validConfigReload) {
    // Schedule to rewrite the configuration file after launch. This way the
    // file is updated after we have done the initial configuration.
    scheduleTimedWrite("{ \"string_test\": \"second value\" }", 100);

412
    // Setup to raise SIGHUP in 200 ms and another at 400 ms.
413
    TimedSignal sighup(*getIOService(), SIGHUP, 200);
414
    TimedSignal sighup2(*getIOService(), SIGHUP, 400);
415

416
    // Write the config and then run launch() for 800 ms
417
    time_duration elapsed_time;
418
    runWithConfig("{ \"string_test\": \"first value\" }", 800, elapsed_time);
419

420
    // Context is still available post launch.
421 422 423 424 425
    // Check to see that our configuration value is what we expect.
    std::string  actual_value = "";
    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
    EXPECT_EQ("second value", actual_value);

426
    // Verify that we saw two occurrences of the signal.
427
    std::vector<int>& signals = controller_->getProcessedSignals();
428
    ASSERT_EQ(2, signals.size());
429
    EXPECT_EQ(SIGHUP, signals[0]);
430
    EXPECT_EQ(SIGHUP, signals[1]);
431 432 433 434
}

// Tests that the SIGINT triggers a normal shutdown.
TEST_F(DStubControllerTest, sigintShutdown) {
435
    // Setup to raise SIGHUP in 1 ms.
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
    TimedSignal sighup(*getIOService(), SIGINT, 1);

    // Write the config and then run launch() for 1000 ms
    time_duration elapsed_time;
    runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);

    // Verify that we saw the signal.
    std::vector<int>& signals = controller_->getProcessedSignals();
    ASSERT_EQ(1, signals.size());
    EXPECT_EQ(SIGINT, signals[0]);

    // Duration should be significantly less than our max run time.
    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
}

// Tests that the SIGTERM triggers a normal shutdown.
TEST_F(DStubControllerTest, sigtermShutdown) {
453
    // Setup to raise SIGHUP in 1 ms.
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
    TimedSignal sighup(*getIOService(), SIGTERM, 1);

    // Write the config and then run launch() for 1000 ms
    time_duration elapsed_time;
    runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);

    // Verify that we saw the signal.
    std::vector<int>& signals = controller_->getProcessedSignals();
    ASSERT_EQ(1, signals.size());
    EXPECT_EQ(SIGTERM, signals[0]);

    // Duration should be significantly less than our max run time.
    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
}

469 470
}; // end of isc::d2 namespace
}; // end of isc namespace