d2_controller_unittests.cc 12.3 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
//
// 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/d2_controller.h>
18
#include <d2/d2_process.h>
19 20
#include <d2/spec_config.h>

21
#include <boost/pointer_cast.hpp>
22 23 24 25 26 27 28 29 30 31 32
#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 {

33 34 35 36 37 38 39 40 41 42 43
/// @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.
44 45 46
class D2ControllerTest : public DControllerTest {
public:
    /// @brief Constructor
47 48
    /// Note the constructor passes in the static D2Controller instance
    /// method.
49 50 51
    D2ControllerTest() : DControllerTest(D2Controller::instance) {
    }

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 79 80 81 82 83 84
    /// @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);
    }
85 86
};

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

97 98 99 100 101
    // 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_));
102 103

    // Verify that controller's spec file name is correct.
104
    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
105 106

    // Verify that controller's IOService exists.
107 108
    EXPECT_TRUE(checkIOService());

109
    // Verify that the Process does NOT exist.
110 111 112
    EXPECT_FALSE(checkProcess());
}

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

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

127
    // Verify that standard options can be parsed without error.
128 129
    EXPECT_NO_THROW(parseArgs(argc, argv));

130
    // Verify that verbose flag is true.
131 132
    EXPECT_TRUE(checkVerbose(true));

133 134 135
    // Verify configuration file name is correct.
    EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));

136
    // Verify that an unknown option is detected.
137
    char* argv2[] = { const_cast<char*>("progName"),
138
                      const_cast<char*>("-x") };
139
    argc = 2;
140
    EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
141 142
}

143 144
/// @brief Tests application process creation and initialization.
/// Verifies that the process can be successfully created and initialized.
145 146 147 148 149
TEST_F(D2ControllerTest, initProcessTesting) {
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());
}

150 151 152 153
/// @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) {
154 155 156
    // Write valid_d2_config and then run launch() for 1000 ms.
    time_duration elapsed_time;
    runWithConfig(valid_d2_config, 1000, elapsed_time);
157

158 159 160
    // Give a generous margin to accomodate slower test environs.
    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
                elapsed_time.total_milliseconds() <= 1300);
161 162
}

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

176
    // Initialize the application process.
177 178 179
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

180 181 182
    // Create a configuration set using a small, valid D2 configuration.
    isc::data::ElementPtr config_set =
                                isc::data::Element::fromJSON(valid_d2_config);
183

184
    // Verify that given a valid config we get a successful update result.
185
    answer = updateConfig(config_set);
186 187
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);
188 189 190 191

    // Use an invalid configuration to verify parsing error return.
    std::string config = "{ \"bogus\": 1000 } ";
    config_set = isc::data::Element::fromJSON(config);
192
    answer = updateConfig(config_set);
193 194
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);
195 196
}

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

209
    // Initialize the application process.
210 211 212
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

213
    // Verify that an unknown command returns an COMMAND_INVALID response.
214
    std::string bogus_command("bogus");
215
    answer = executeCommand(bogus_command, arg_set);
216 217
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_INVALID, rcode);
218 219 220

    // Verify that shutdown command returns COMMAND_SUCCESS response.
    //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
221
    answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
222 223
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 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 317 318 319 320 321 322
}

// 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
    // valid_d2_config (see d_test_stubs.cc)
    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 =
            "{"
            " \"ip_address\": \"192.168.77.1\" , "
            " \"port\": 777 , "
            "\"tsig_keys\": [], "
            "\"forward_ddns\" : {}, "
            "\"reverse_ddns\" : {} "
            "}";

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

324
    /// @todo add a way to trap log file and search it
325 326 327 328
}

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