d2_controller_unittests.cc 12.3 KB
Newer Older
1
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// 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.

15 16
#include <config.h>

17 18 19
#include <config/ccsession.h>
#include <d_test_stubs.h>
#include <d2/d2_controller.h>
20
#include <d2/d2_process.h>
21 22
#include <d2/spec_config.h>

23
#include <boost/pointer_cast.hpp>
24 25 26 27 28 29 30 31 32 33
#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>

#include <sstream>

using namespace boost::posix_time;

namespace isc {
namespace d2 {

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

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 85
    /// @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);
    }
86 87
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Verify that shutdown command returns COMMAND_SUCCESS response.
    //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
222
    answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
223 224
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);
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 323
}

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

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

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