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
    DStubControllerPtr controller_;
45 46 47 48 49
};

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

56 57 58 59 60
    // 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_));
61 62

    // Verify that controller's spec file name is correct.
63
    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
64 65

    // Verify that controller's IOService exists.
66 67
    EXPECT_TRUE(checkIOService());

68
    // Verify that the Process does NOT exist.
69 70 71
    EXPECT_FALSE(checkProcess());
}

72 73 74 75 76 77
/// @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.
78 79
TEST_F(DStubControllerTest, commandLineArgs) {

80
    // Verify that verbose flag is false initially.
81 82
    EXPECT_TRUE(checkVerbose(false));

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

91
    // Verify that verbose is true.
92 93
    EXPECT_TRUE(checkVerbose(true));

94 95 96
    // Verify configuration file name is correct
    EXPECT_TRUE(checkConfigFileName("cfgName"));

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

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

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

118 119 120 121 122 123
/// @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.
124
TEST_F(DStubControllerTest, initProcessTesting) {
125
    // Verify that a failure during process creation is caught.
126 127 128 129
    SimFailure::set(SimFailure::ftCreateProcessException);
    EXPECT_THROW(initProcess(), DControllerBaseError);
    EXPECT_FALSE(checkProcess());

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

135
    // Re-create controller, verify that we are starting clean
136
    resetController();
137 138 139
    EXPECT_FALSE(checkProcess());

    // Verify that an error during process initialization is handled.
140 141 142
    SimFailure::set(SimFailure::ftProcessInit);
    EXPECT_THROW(initProcess(), DProcessBaseError);

143
    // Re-create controller, verify that we are starting clean
144 145 146
    resetController();
    EXPECT_FALSE(checkProcess());

147
    // Verify that the application process can created and initialized.
148 149 150 151
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());
}

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

    // Launch the controller in integrated mode.
162
    EXPECT_THROW(launch(argc, argv), InvalidUsage);
163 164 165 166
}

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

    // Launch the controller in stand alone mode.
    SimFailure::set(SimFailure::ftCreateProcessException);
179
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
180 181
}

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

    // 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.
194 195
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
196 197
}

198 199 200 201 202 203
/// @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"),
204
                     const_cast<char*>("-d") };
205 206 207 208 209 210 211 212 213 214 215
    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"),
216
                     const_cast<char*>("-d") };
217 218 219 220 221 222 223 224 225 226
    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"),
227
                     const_cast<char*>("-d") };
228 229 230 231 232 233
    int argc = 2;

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

234 235 236
/// @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
237
/// and no simulated errors.  Launch should throw ProcessRunError.
238 239 240 241
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());
242
    timer.setup(genFatalErrorCallback, 2000);
243

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

    // 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.
251 252
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
253 254
}

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

268
    // Initialize the application process.
269 270 271
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

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

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

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

289 290
/// @brief Command execution tests.
/// This really tests just the ability of the handler to invoke the necessary
291
/// chain of methods and to handle error conditions.
292 293 294 295 296 297 298 299 300 301
/// 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.
302 303 304 305 306
TEST_F(DStubControllerTest, executeCommandTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;
    isc::data::ElementPtr arg_set;

307
    // Initialize the application process.
308 309 310
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

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

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

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

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

    // Verify that a valid custom controller command that fails returns
    // a d2::COMMAND_ERROR.
    SimFailure::set(SimFailure::ftControllerCommand);
337
    answer = executeCommand(DStubController::stub_ctl_command_, arg_set);
338 339 340 341 342 343
    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);
344
    answer = executeCommand(DStubProcess::stub_proc_command_, arg_set);
345 346
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_ERROR, rcode);
347 348
}

349 350 351 352 353 354
// 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);

355
    // Setup to raise SIGHUP in 10 ms.
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    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);

380
    // Setup to raise SIGHUP in 200 ms.
381 382 383
    TimedSignal sighup(*getIOService(), SIGHUP, 200);

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

390
    // Context is still available post launch. Check to see that our
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    // 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);

409
    // Setup to raise SIGHUP in 200 ms and another at 400 ms.
410
    TimedSignal sighup(*getIOService(), SIGHUP, 200);
411
    TimedSignal sighup2(*getIOService(), SIGHUP, 400);
412

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

417
    // Context is still available post launch.
418 419 420 421 422
    // 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);

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

// Tests that the SIGINT triggers a normal shutdown.
TEST_F(DStubControllerTest, sigintShutdown) {
432
    // Setup to raise SIGHUP in 1 ms.
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    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) {
450
    // Setup to raise SIGHUP in 1 ms.
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    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);
}

466 467
}; // end of isc::d2 namespace
}; // end of isc namespace