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
    /// @brief Destructor
53 54
    ~D2ControllerTest() {
    }
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 86 87 88

    /// @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);
    }
89 90
};

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

101 102 103 104 105
    // 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_));
106 107

    // Verify that controller's spec file name is correct.
108
    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
109 110

    // Verify that controller's IOService exists.
111 112
    EXPECT_TRUE(checkIOService());

113
    // Verify that the Process does NOT exist.
114 115 116
    EXPECT_FALSE(checkProcess());
}

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

128
    // Verify that verbose flag is false initially.
129 130
    EXPECT_TRUE(checkVerbose(false));

131
    // Verify that standard options can be parsed without error.
132 133
    EXPECT_NO_THROW(parseArgs(argc, argv));

134
    // Verify that verbose flag is true.
135 136
    EXPECT_TRUE(checkVerbose(true));

137 138 139
    // Verify configuration file name is correct.
    EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));

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

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

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

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

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

180
    // Initialize the application process.
181 182 183
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

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

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

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

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

213
    // Initialize the application process.
214 215 216
    ASSERT_NO_THROW(initProcess());
    EXPECT_TRUE(checkProcess());

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

    // Verify that shutdown command returns COMMAND_SUCCESS response.
    //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
225
    answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
226 227
    isc::config::parseAnswer(rcode, answer);
    EXPECT_EQ(COMMAND_SUCCESS, rcode);
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 324 325 326
}

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

328
    /// @todo add a way to trap log file and search it
329 330 331 332
}

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