d2_controller_unittests.cc 11.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 8
#include <config.h>

9
#include <cc/command_interpreter.h>
10
#include <d2/d2_controller.h>
11
#include <d2/d2_process.h>
12 13
#include <process/spec_config.h>
#include <process/testutils/d_test_stubs.h>
14

15
#include <boost/pointer_cast.hpp>
16 17 18 19 20
#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>

#include <sstream>

21
using namespace isc::process;
22 23 24 25 26
using namespace boost::posix_time;

namespace isc {
namespace d2 {

27 28 29 30 31 32 33 34 35 36 37
/// @brief Test fixture class for testing D2Controller class. This class
/// derives from DControllerTest and wraps a D2Controller.  Much of the
/// underlying functionality is in the DControllerBase class which has an
/// extensive set of unit tests that are independent of DHCP-DDNS.
/// @TODO Currently These tests are relatively light and duplicate some of
/// the testing done on the base class.  These tests are sufficient to ensure
/// that D2Controller properly derives from its base class and to test the
/// logic that is unique to D2Controller. These tests will be augmented and
/// or new tests added as additional functionality evolves.
/// Unlike the stub testing, there is no use of SimFailure to induce error
/// conditions as this is production code.
38 39 40
class D2ControllerTest : public DControllerTest {
public:
    /// @brief Constructor
41 42
    /// Note the constructor passes in the static D2Controller instance
    /// method.
43 44 45
    D2ControllerTest() : DControllerTest(D2Controller::instance) {
    }

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    /// @brief Fetches the D2Controller's D2Process
    ///
    /// @return A pointer to the process which may be null if it has not yet
    /// been instantiated.
    D2ProcessPtr getD2Process() {
        return (boost::dynamic_pointer_cast<D2Process>(getProcess()));
    }

    /// @brief Fetches the D2Process's D2Configuration manager
    ///
    /// @return A pointer to the manager which may be null if it has not yet
    /// been instantiated.
    D2CfgMgrPtr getD2CfgMgr() {
        D2CfgMgrPtr p;
        if (getD2Process()) {
            p = getD2Process()->getD2CfgMgr();
        }

        return (p);
    }

    /// @brief Fetches the D2Configuration manager's D2CfgContext
    ///
    /// @return A pointer to the context which may be null if it has not yet
    /// been instantiated.
    D2CfgContextPtr getD2CfgContext() {
        D2CfgContextPtr p;
        if (getD2CfgMgr()) {
            p = getD2CfgMgr()->getD2CfgContext();
        }

        return (p);
    }
79 80
};

81
/// @brief Basic Controller instantiation testing.
82
/// Verifies that the controller singleton gets created and that the
83
/// basic derivation from the base class is intact.
84
TEST_F(D2ControllerTest, basicInstanceTesting) {
85 86
    // Verify the we can the singleton instance can be fetched and that
    // it is the correct type.
87 88
    DControllerBasePtr& controller = DControllerTest::getController();
    ASSERT_TRUE(controller);
89 90
    ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));

91 92 93 94 95
    // Verify that controller's app name is correct.
    EXPECT_TRUE(checkAppName(D2Controller::d2_app_name_));

    // Verify that controller's bin name is correct.
    EXPECT_TRUE(checkBinName(D2Controller::d2_bin_name_));
96 97

    // Verify that controller's spec file name is correct.
98
    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
99 100

    // Verify that controller's IOService exists.
101 102
    EXPECT_TRUE(checkIOService());

103
    // Verify that the Process does NOT exist.
104 105 106
    EXPECT_FALSE(checkProcess());
}

107
/// @brief Tests basic command line processing.
108
/// Verifies that:
109 110
/// 1. Standard command line options are supported.
/// 2. Invalid options are detected.
111
TEST_F(D2ControllerTest, commandLineArgs) {
112
    char* argv[] = { const_cast<char*>("progName"),
113 114
                     const_cast<char*>("-c"),
                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
115
                     const_cast<char*>("-d") };
116
    int argc = 4;
117

118
    // Verify that verbose flag is false initially.
119 120
    EXPECT_TRUE(checkVerbose(false));

121
    // Verify that standard options can be parsed without error.
122 123
    EXPECT_NO_THROW(parseArgs(argc, argv));

124
    // Verify that verbose flag is true.
125 126
    EXPECT_TRUE(checkVerbose(true));

127 128 129
    // Verify configuration file name is correct.
    EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));

130
    // Verify that an unknown option is detected.
131
    char* argv2[] = { const_cast<char*>("progName"),
132
                      const_cast<char*>("-x") };
133
    argc = 2;
134
    EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
135 136
}

137 138
/// @brief Tests application process creation and initialization.
/// Verifies that the process can be successfully created and initialized.
139 140 141 142 143
TEST_F(D2ControllerTest, initProcessTesting) {
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());
}

144 145 146 147
/// @brief Tests launch and normal shutdown (stand alone mode).
/// This creates an interval timer to generate a normal shutdown and then
/// launches with a valid, stand-alone command line and no simulated errors.
TEST_F(D2ControllerTest, launchNormalShutdown) {
148 149 150
    // Write valid_d2_config and then run launch() for 1000 ms.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 1000, elapsed_time);
151

152 153 154
    // Give a generous margin to accomodate slower test environs.
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
                elapsed_time.total_milliseconds() <= 1300);
155 156
}

157
/// @brief Configuration update event testing.
158
/// This really tests just the ability of the handlers to invoke the necessary
159 160
/// chain of methods and handle error conditions. Configuration parsing and
/// retrieval should be tested as part of the d2 configuration management
161
/// implementation.
162
/// This test verifies that:
163 164
/// 1. A valid configuration yields a successful parse result.
/// 2. That an application process error in configuration updating is handled
165
/// properly.
166 167 168 169
TEST_F(D2ControllerTest, configUpdateTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;

170
    // Initialize the application process.
171 172 173
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

174 175 176
    // Create a configuration set using a small, valid D2 configuration.
    isc::data::ElementPtr config_set =
                                isc::data::Element::fromJSON(valid_d2_config);
177

178
    // Verify that given a valid config we get a successful update result.
179
    answer = updateConfig(config_set);
180 181
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);
182 183 184 185

    // Use an invalid configuration to verify parsing error return.
    std::string config = "{ \"bogus\": 1000 } ";
    config_set = isc::data::Element::fromJSON(config);
186
    answer = updateConfig(config_set);
187 188
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);
189 190
}

191 192
/// @brief Command execution tests.
/// This really tests just the ability of the handler to invoke the necessary
193
/// chain of methods and to handle error conditions.
194 195 196 197
/// 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.
198 199 200 201 202
TEST_F(D2ControllerTest, executeCommandTests) {
    int rcode = -1;
    isc::data::ConstElementPtr answer;
    isc::data::ElementPtr arg_set;

203
    // Initialize the application process.
204 205 206
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

207
    // Verify that an unknown command returns an COMMAND_INVALID response.
208
    std::string bogus_command("bogus");
209
    answer = executeCommand(bogus_command, arg_set);
210 211
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_INVALID, rcode);
212 213 214

    // Verify that shutdown command returns COMMAND_SUCCESS response.
    //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
215
    answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
216 217
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
}

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

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

    // Write valid_d2_config and then run launch() for a maximum of 500 ms.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 500, elapsed_time);

    // Context is still available post launch.
    // Check to see that our configuration matches the original per
236
    // valid_d2_config (see src/lib/process/testutils/d_test_stubs.cc)
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
    D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
    ASSERT_TRUE(d2_params);

    EXPECT_EQ("127.0.0.1", d2_params->getIpAddress().toText());
    EXPECT_EQ(5031, d2_params->getPort());
    EXPECT_TRUE(d2_cfg_mgr->forwardUpdatesEnabled());
    EXPECT_TRUE(d2_cfg_mgr->reverseUpdatesEnabled());

    /// @todo add a way to trap log file and search it
}

// Tests that the original configuration is replaced after a SIGHUP triggered
// reconfiguration succeeds.
TEST_F(D2ControllerTest, validConfigReload) {
    // Define a replacement config.
    const char* second_cfg =
            "{"
255
            " \"ip-address\": \"192.168.77.1\" , "
256
            " \"port\": 777 , "
257 258 259
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 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
            "}";

    // Schedule to replace the configuration file after launch. This way the
    // file is updated after we have done the initial configuration.
    scheduleTimedWrite(second_cfg, 100);

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

    // Write valid_d2_config and then run launch() for a maximum of 500ms.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 500, elapsed_time);

    // Context is still available post launch.
    // Check to see that our configuration matches the replacement config.
    D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
    D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
    ASSERT_TRUE(d2_params);

    EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
    EXPECT_EQ(777, d2_params->getPort());
    EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
    EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());

    /// @todo add a way to trap log file and search it
}

// Tests that the SIGINT triggers a normal shutdown.
TEST_F(D2ControllerTest, sigintShutdown) {
    // Setup to raise SIGHUP in 1 ms.
    TimedSignal sighup(*getIOService(), SIGINT, 1);

    // Write valid_d2_config and then run launch() for a maximum of 1000 ms.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 1000, elapsed_time);

    // Signaled shutdown should make our elapsed time much smaller than
    // the maximum run time.  Give generous margin to accomodate slow
    // test environs.
    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);

    /// @todo add a way to trap log file and search it
}

// Tests that the SIGTERM triggers a normal shutdown.
TEST_F(D2ControllerTest, sigtermShutdown) {
    // Setup to raise SIGHUP in 1 ms.
    TimedSignal sighup(*getIOService(), SIGTERM, 1);

    // Write valid_d2_config and then run launch() for a maximum of 1 s.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 1000, elapsed_time);

    // Signaled shutdown should make our elapsed time much smaller than
    // the maximum run time.  Give generous margin to accomodate slow
    // test environs.
    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
317

318
    /// @todo add a way to trap log file and search it
319 320 321 322
}

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