d_controller_unittests.cc 17.4 KB
Newer Older
1
// Copyright (C) 2013-2017 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
#include <kea_version.h>
9

10
#include <cc/command_interpreter.h>
11
#include <process/testutils/d_test_stubs.h>
12 13 14 15 16 17 18 19 20

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

#include <sstream>

using namespace boost::posix_time;

namespace isc {
21
namespace process {
22

23 24 25 26 27 28 29 30 31
/// @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) {
32 33 34
        controller_ = boost::dynamic_pointer_cast<DStubController>
                                                 (DControllerTest::
                                                  getController());
35 36
    }

37
    DStubControllerPtr controller_;
38 39 40
};

/// @brief Basic Controller instantiation testing.
Andrei Pavel's avatar
Andrei Pavel committed
41
/// Verifies that the controller singleton gets created and that the
42
/// basic derivation from the base class is intact.
43
TEST_F(DStubControllerTest, basicInstanceTesting) {
44
    // Verify that the singleton exists and it is the correct type.
45 46
    DControllerBasePtr& controller = DControllerTest::getController();
    ASSERT_TRUE(controller);
47 48
    ASSERT_NO_THROW(boost::dynamic_pointer_cast<DStubController>(controller));

49 50 51 52 53
    // 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_));
54 55

    // Verify that controller's IOService exists.
56 57
    EXPECT_TRUE(checkIOService());

58
    // Verify that the Process does NOT exist.
59 60 61
    EXPECT_FALSE(checkProcess());
}

62 63 64 65 66 67
/// @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.
68 69
TEST_F(DStubControllerTest, commandLineArgs) {

70
    // Verify that verbose flag is false initially.
71 72
    EXPECT_TRUE(checkVerbose(false));

73
    // Verify that standard options can be parsed without error.
74
    char* argv[] = { const_cast<char*>("progName"),
75 76
                     const_cast<char*>("-c"),
                     const_cast<char*>("cfgName"),
77
                     const_cast<char*>("-d") };
78
    int argc = 4;
79 80
    EXPECT_NO_THROW(parseArgs(argc, argv));

81
    // Verify that verbose is true.
82 83
    EXPECT_TRUE(checkVerbose(true));

84 85 86
    // Verify configuration file name is correct
    EXPECT_TRUE(checkConfigFileName("cfgName"));

87
    // Verify that the custom command line option is parsed without error.
88 89 90
    char xopt[3] = "- ";
    xopt[1] =  *DStubController::stub_option_x_;
    char* argv1[] = { const_cast<char*>("progName"), xopt};
91 92 93
    argc = 2;
    EXPECT_NO_THROW (parseArgs(argc, argv1));

94
    // Verify that an unknown option is detected.
95 96
    char* argv2[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-bs") };
97 98
    argc = 2;
    EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
99 100

    // Verify that extraneous information is detected.
101 102 103
    char* argv3[] = { const_cast<char*>("progName"),
                      const_cast<char*>("extra"),
                      const_cast<char*>("information") };
104 105
    argc = 3;
    EXPECT_THROW (parseArgs(argc, argv3), InvalidUsage);
106 107
}

108 109 110 111 112 113
/// @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.
114
TEST_F(DStubControllerTest, initProcessTesting) {
115
    // Verify that a failure during process creation is caught.
116 117 118 119
    SimFailure::set(SimFailure::ftCreateProcessException);
    EXPECT_THROW(initProcess(), DControllerBaseError);
    EXPECT_FALSE(checkProcess());

120
    // Verify that a NULL returned by process creation is handled.
121 122 123 124
    SimFailure::set(SimFailure::ftCreateProcessNull);
    EXPECT_THROW(initProcess(), DControllerBaseError);
    EXPECT_FALSE(checkProcess());

125
    // Re-create controller, verify that we are starting clean
126
    resetController();
127 128 129
    EXPECT_FALSE(checkProcess());

    // Verify that an error during process initialization is handled.
130 131 132
    SimFailure::set(SimFailure::ftProcessInit);
    EXPECT_THROW(initProcess(), DProcessBaseError);

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

137
    // Verify that the application process can created and initialized.
138 139 140 141
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());
}

142
/// @brief Tests launch handling of invalid command line.
143 144
/// This test launches with an invalid command line which should throw
/// an InvalidUsage.
145 146
TEST_F(DStubControllerTest, launchInvalidUsage) {
    // Command line to run integrated
147 148
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-z") };
149 150 151
    int argc = 2;

    // Launch the controller in integrated mode.
152
    EXPECT_THROW(launch(argc, argv), InvalidUsage);
153 154 155 156
}

/// @brief Tests launch handling of failure in application process
/// initialization.  This test launches with a valid command line but with
157 158
/// SimFailure set to fail during process creation.  Launch should throw
/// ProcessInitError.
159 160
TEST_F(DStubControllerTest, launchProcessInitError) {
    // Command line to run integrated
161
    char* argv[] = { const_cast<char*>("progName"),
162 163
                     const_cast<char*>("-c"),
                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
164
                     const_cast<char*>("-d") };
165
    int argc = 4;
166 167 168

    // Launch the controller in stand alone mode.
    SimFailure::set(SimFailure::ftCreateProcessException);
169
    EXPECT_THROW(launch(argc, argv), ProcessInitError);
170 171
}

172 173
/// @brief Tests launch and normal shutdown (stand alone mode).
/// This creates an interval timer to generate a normal shutdown and then
174 175
/// launches with a valid, command line, with a valid configuration file
///  and no simulated errors.
176
TEST_F(DStubControllerTest, launchNormalShutdown) {
177 178 179
    // Write the valid, empty, config and then run launch() for 1000 ms
    time_duration elapsed_time;
    ASSERT_NO_THROW(runWithConfig("{}", 2000, elapsed_time));
180 181 182 183

    // 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.
184 185
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
186 187
}

Andrei Pavel's avatar
Andrei Pavel committed
188
/// @brief Tests launch with an non-existing configuration file.
189
TEST_F(DStubControllerTest, nonExistingConfigFile) {
190 191 192 193
    // command line to run standalone
    char* argv[] = { const_cast<char*>("progName"),
                     const_cast<char*>("-c"),
                     const_cast<char*>("bogus-file"),
194
                     const_cast<char*>("-d") };
195 196 197 198 199 200 201 202 203 204 205
    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"),
206
                     const_cast<char*>("-d") };
207 208 209 210 211 212 213 214 215 216
    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"),
217
                     const_cast<char*>("-d") };
218 219 220
    int argc = 2;

    // Record start time, and invoke launch().
221
    EXPECT_THROW(launch(argc, argv), LaunchError);
222 223
}

224 225
/// @brief Tests launch with an operational error during application execution.
/// This test creates an interval timer to generate a runtime exception during
Josh Soref's avatar
Josh Soref committed
226
/// the process event loop. It launches with a valid, stand-alone command line
227
/// and no simulated errors.  Launch should throw ProcessRunError.
228 229 230 231
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());
232
    timer.setup(genFatalErrorCallback, 2000);
233

234
    // Write the valid, empty, config and then run launch() for 5000 ms
235
    time_duration elapsed_time;
236
    EXPECT_THROW(runWithConfig("{}", 5000, elapsed_time), ProcessRunError);
237 238 239 240

    // 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.
241 242
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
                elapsed_time.total_milliseconds() <= 2300);
243 244
}

245
/// @brief Configuration update event testing.
246
/// This really tests just the ability of the handlers to invoke the necessary
247 248
/// chain of methods and handle error conditions. Configuration parsing and
/// retrieval should be tested as part of the d2 configuration management
249
/// implementation.
250
/// This test verifies that:
251 252
/// 1. That a valid configuration update results in successful status return.
/// 2. That an application process error in configuration updating is handled
253
/// properly.
254 255 256 257
TEST_F(DStubControllerTest, configUpdateTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;

258
    // Initialize the application process.
259 260 261
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

262
    // Create a configuration set. Content is arbitrary, just needs to be
263 264 265 266
    // valid JSON.
    std::string config = "{ \"test-value\": 1000 } ";
    isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);

267
    // Verify that a valid config gets a successful update result.
268
    answer = updateConfig(config_set);
269 270 271 272
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);

    // Verify that a valid config gets a successful check result.
273
    answer = checkConfig(config_set);
274 275 276 277 278
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);

    // Verify that an error in process configure method is handled.
    SimFailure::set(SimFailure::ftProcessConfigure);
279
    answer = updateConfig(config_set);
280 281 282 283 284
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);

    // Verify that an error is handled too when the config is checked for.
    SimFailure::set(SimFailure::ftProcessConfigure);
285
    answer = checkConfig(config_set);
286 287 288 289
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);
}

290 291 292 293 294 295
// 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);

296
    // Setup to raise SIGHUP in 10 ms.
297
    TimedSignal sighup(*getIOService(), SIGHUP, 10);
298 299
    TimedSignal sigint(*getIOService(), SIGINT, 100);
    TimedSignal sigterm(*getIOService(), SIGTERM, 200);
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

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

321
    // Setup to raise SIGHUP in 200 ms.
322 323 324
    TimedSignal sighup(*getIOService(), SIGHUP, 200);

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

331
    // Context is still available post launch. Check to see that our
332 333 334 335 336 337 338 339 340 341 342
    // 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]);
}

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
// Tests that the original configuration is retained after a SIGHUP triggered
// reconfiguration fails due to invalid config content.
TEST_F(DStubControllerTest, alternateParsing) {
    controller_->useAlternateParser(true);

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

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

    // Context is still available post launch. Check to see that our
    // configuration value is still the original value.
    std::string  actual_value = "";
    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
    EXPECT_EQ("alt 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]);
}



372 373 374 375 376 377 378
// 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);

379
    // Setup to raise SIGHUP in 200 ms and another at 400 ms.
380
    TimedSignal sighup(*getIOService(), SIGHUP, 200);
381
    TimedSignal sighup2(*getIOService(), SIGHUP, 400);
382

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

387
    // Context is still available post launch.
388 389 390 391 392
    // 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);

393
    // Verify that we saw two occurrences of the signal.
394
    std::vector<int>& signals = controller_->getProcessedSignals();
395
    ASSERT_EQ(2, signals.size());
396
    EXPECT_EQ(SIGHUP, signals[0]);
397
    EXPECT_EQ(SIGHUP, signals[1]);
398 399 400 401
}

// Tests that the SIGINT triggers a normal shutdown.
TEST_F(DStubControllerTest, sigintShutdown) {
402
    // Setup to raise SIGHUP in 1 ms.
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
    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);
}

418 419 420 421 422 423 424 425 426 427 428
// Verifies that version and extended version information is correct
TEST_F(DStubControllerTest, getVersion) {
    std::string text = controller_->getVersion(false);
    EXPECT_EQ(text,VERSION);

    text = controller_->getVersion(true);
    EXPECT_NE(std::string::npos, text.find(VERSION));
    EXPECT_NE(std::string::npos, text.find(EXTENDED_VERSION));
    EXPECT_NE(std::string::npos, text.find(controller_->getVersionAddendum()));
}

429 430
// Tests that the SIGTERM triggers a normal shutdown.
TEST_F(DStubControllerTest, sigtermShutdown) {
431
    // Setup to raise SIGHUP in 1 ms.
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
    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);
}

447
}; // end of isc::process namespace
448
}; // end of isc namespace