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
    TimedSignal sighup(*getIOService(), SIGHUP, 10);
357 358
    TimedSignal sigint(*getIOService(), SIGINT, 100);
    TimedSignal sigterm(*getIOService(), SIGTERM, 200);
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379

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