diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/agent/tests/ca_controller_unittests.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/agent/tests/ca_controller_unittests.cc')
-rw-r--r-- | src/bin/agent/tests/ca_controller_unittests.cc | 837 |
1 files changed, 837 insertions, 0 deletions
diff --git a/src/bin/agent/tests/ca_controller_unittests.cc b/src/bin/agent/tests/ca_controller_unittests.cc new file mode 100644 index 0000000..f7b2c8b --- /dev/null +++ b/src/bin/agent/tests/ca_controller_unittests.cc @@ -0,0 +1,837 @@ +// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC") +// +// 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/. + +#include <config.h> + +#include <asiolink/testutils/timed_signal.h> +#include <agent/ca_controller.h> +#include <agent/ca_process.h> +#include <agent/ca_command_mgr.h> +#include <cc/data.h> +#include <cc/command_interpreter.h> +#include <process/testutils/d_test_stubs.h> +#include <boost/pointer_cast.hpp> +#include <sstream> +#include <unistd.h> + +using namespace isc::asiolink::test; +using namespace isc::agent; +using namespace isc::data; +using namespace isc::http; +using namespace isc::process; +using namespace boost::posix_time; +using namespace std; + +namespace { + +/// @brief Valid Control Agent Config used in tests. +const char* valid_agent_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8081," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp6/socket\"" + " }" + " }" + "}"; + +/// @brief test fixture class for testing CtrlAgentController class. +/// +/// This class derives from DControllerTest and wraps CtrlAgentController. Much +/// of the underlying functionality is in the DControllerBase class which +/// has extensive set of unit tests that are independent from the Control +/// Agent. +class CtrlAgentControllerTest : public DControllerTest { +public: + + /// @brief Constructor. + CtrlAgentControllerTest() + : DControllerTest(CtrlAgentController::instance) { + } + + /// @brief Returns pointer to CtrlAgentProcess instance. + CtrlAgentProcessPtr getCtrlAgentProcess() { + return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess())); + } + + /// @brief Returns pointer to CtrlAgentCfgMgr instance for a process. + CtrlAgentCfgMgrPtr getCtrlAgentCfgMgr() { + CtrlAgentCfgMgrPtr p; + if (getCtrlAgentProcess()) { + p = getCtrlAgentProcess()->getCtrlAgentCfgMgr(); + } + return (p); + } + + /// @brief Returns a pointer to the configuration context. + CtrlAgentCfgContextPtr getCtrlAgentCfgContext() { + CtrlAgentCfgContextPtr p; + if (getCtrlAgentCfgMgr()) { + p = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext(); + } + return (p); + } + + /// @brief Tests that socket info structure contains 'unix' socket-type + /// value and the expected socket-name. + /// + /// @param service Service type. + /// @param exp_socket_name Expected socket name. + void testUnixSocketInfo(const std::string& service, + const std::string& exp_socket_name) { + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + ConstElementPtr sock_info = ctx->getControlSocketInfo(service); + ASSERT_TRUE(sock_info); + ASSERT_TRUE(sock_info->contains("socket-type")); + EXPECT_EQ("unix", sock_info->get("socket-type")->stringValue()); + ASSERT_TRUE(sock_info->contains("socket-name")); + EXPECT_EQ(exp_socket_name, + sock_info->get("socket-name")->stringValue()); + } + + /// @brief Compares the status in the given parse result to a given value. + /// + /// @param answer Element set containing an integer response and string + /// comment. + /// @param exp_status is an integer against which to compare the status. + /// @param exp_txt is expected text (not checked if "") + /// + void checkAnswer(isc::data::ConstElementPtr answer, + int exp_status, + string exp_txt = "") { + int rcode = 0; + isc::data::ConstElementPtr comment; + comment = isc::config::parseAnswer(rcode, answer); + + if (rcode != exp_status) { + ADD_FAILURE() << "Expected status code " << exp_status + << " but received " << rcode << ", comment: " + << (comment ? comment->str() : "(none)"); + } + + // Ok, parseAnswer interface is weird. If there are no arguments, + // it returns content of text. But if there is an argument, + // it returns the argument and it's not possible to retrieve + // "text" (i.e. comment). + if (comment->getType() != Element::string) { + comment = answer->get("text"); + } + + if (!exp_txt.empty()) { + EXPECT_EQ(exp_txt, comment->stringValue()); + } + } + + /// @brief Checks whether specified command is registered + /// + /// @param name name of the command to be checked + /// @param expect_true true - must be registered, false - must not be + void checkCommandRegistered(const std::string& name, bool expect_true = true) { + + // First get the list of registered commands + ConstElementPtr lst = Element::fromJSON("{ \"command\": \"list-commands\" }"); + ConstElementPtr rsp = CtrlAgentCommandMgr::instance().processCommand(lst); + + // The response must be an array with at least one element + ASSERT_TRUE(rsp); + ASSERT_EQ(Element::list, rsp->getType()); + ASSERT_LE(1, rsp->size()); + ConstElementPtr args = rsp->get(0)->get("arguments"); + ASSERT_TRUE(args); + + string args_txt = args->str(); + + if (expect_true) { + EXPECT_TRUE(args_txt.find(name) != string::npos); + } else { + EXPECT_TRUE(args_txt.find(name) == string::npos); + } + } + +}; + +// Basic Controller instantiation testing. +// Verifies that the controller singleton gets created and that the +// basic derivation from the base class is intact. +TEST_F(CtrlAgentControllerTest, basicInstanceTesting) { + // Verify the singleton instance can be fetched and that + // it has the correct type. + DControllerBasePtr& controller = DControllerTest::getController(); + ASSERT_TRUE(controller); + ASSERT_NO_THROW(boost::dynamic_pointer_cast<CtrlAgentController>(controller)); + + // Verify that controller's app name is correct. + EXPECT_TRUE(checkAppName(CtrlAgentController::agent_app_name_)); + + // Verify that controller's bin name is correct. + EXPECT_TRUE(checkBinName(CtrlAgentController::agent_bin_name_)); + + // Verify that controller's IOService exists. + EXPECT_TRUE(checkIOService()); + + // Verify that the Process does NOT exist. + EXPECT_FALSE(checkProcess()); +} + + +// Tests basic command line processing. +// Verifies that: +// 1. Standard command line options are supported. +// 2. Invalid options are detected. +TEST_F(CtrlAgentControllerTest, commandLineArgs) { + char* argv[] = { const_cast<char*>("progName"), + const_cast<char*>("-c"), + const_cast<char*>(DControllerTest::CFG_TEST_FILE), + const_cast<char*>("-d") }; + int argc = 4; + + // Verify that verbose flag is false initially. + EXPECT_TRUE(checkVerbose(false)); + + // Verify that standard options can be parsed without error. + EXPECT_NO_THROW(parseArgs(argc, argv)); + + // Verify that verbose flag is true. + EXPECT_TRUE(checkVerbose(true)); + + // Verify configuration file name is correct. + EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE)); + + // Verify that an unknown option is detected. + char* argv2[] = { const_cast<char*>("progName"), + const_cast<char*>("-x") }; + argc = 2; + EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage); +} + +// Tests application process creation and initialization. +// Verifies that the process can be successfully created and initialized. +TEST_F(CtrlAgentControllerTest, initProcessTesting) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); +} + +// 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(CtrlAgentControllerTest, launchNormalShutdown) { + // Write valid_agent_config and then run launch() for 1000 ms. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 1000, elapsed_time); + + // Give a generous margin to accommodate slower test environs. + EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 && + elapsed_time.total_milliseconds() <= 1300); +} + +// Tests that the SIGINT triggers a normal shutdown. +TEST_F(CtrlAgentControllerTest, sigintShutdown) { + // Setup to raise SIGHUP in 1 ms. + TimedSignal sighup(*getIOService(), SIGINT, 1); + + // Write valid_agent_config and then run launch() for a maximum + // of 1000 ms. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 1000, elapsed_time); + + // Signaled shutdown should make our elapsed time much smaller than + // the maximum run time. Give generous margin to accommodate slow + // test environs. + EXPECT_TRUE(elapsed_time.total_milliseconds() < 300); +} + +// Tests that the SIGTERM triggers a normal shutdown. +TEST_F(CtrlAgentControllerTest, sigtermShutdown) { + // Setup to raise SIGTERM in 1 ms. + TimedSignal sighup(*getIOService(), SIGTERM, 1); + + // Write valid_agent_config and then run launch() for a maximum of 1 s. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 1000, elapsed_time); + + // Signaled shutdown should make our elapsed time much smaller than + // the maximum run time. Give generous margin to accommodate slow + // test environs. + EXPECT_TRUE(elapsed_time.total_milliseconds() < 300); +} + +// Tests that the sockets settings are updated upon successful reconfiguration. +TEST_F(CtrlAgentControllerTest, successfulConfigUpdate) { + // This configuration should be used to override the initial configuration. + const char* second_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8080," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // Check that the HTTP listener still exists after reconfiguration. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + // The listener should have been reconfigured to use new address and port. + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8080, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The server should now hold the new listener configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8080, ctx->getHttpPort()); + + // The forwarding configuration should have been updated too. + testUnixSocketInfo("dhcp4", "/second/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/second/dhcp6/socket"); + + // After the shutdown the HTTP listener no longer exists. + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that the server continues to use an old configuration when the listener +// reconfiguration is unsuccessful. +TEST_F(CtrlAgentControllerTest, unsuccessfulConfigUpdate) { + // This is invalid configuration. We're using restricted port number and + // IP address of 1.1.1.1. + const char* second_config = + "{" + " \"http-host\": \"1.1.1.1\"," + " \"http-port\": 1," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // We should still be using an original listener. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8081, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The reconfiguration should have been unsuccessful, and the server should + // still use the original configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8081, ctx->getHttpPort()); + + // Same for forwarding. + testUnixSocketInfo("dhcp4", "/first/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/first/dhcp6/socket"); + + // After the shutdown the HTTP listener no longer exists. + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that it is possible to update the configuration in such a way that the +// listener configuration remains the same. The server should continue using the +// listener instance it has been using prior to the reconfiguration. +TEST_F(CtrlAgentControllerTest, noListenerChange) { + // This configuration should be used to override the initial configuration. + const char* second_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8081," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // Check that the HTTP listener still exists after reconfiguration. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8081, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The server should use a correct listener configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8081, ctx->getHttpPort()); + + // The forwarding configuration should have been updated. + testUnixSocketInfo("dhcp4", "/second/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/second/dhcp6/socket"); + + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that registerCommands actually registers anything. +TEST_F(CtrlAgentControllerTest, registeredCommands) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl = + boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + ctrl->registerCommands(); + + // Check that the following command are really available. + checkCommandRegistered("build-report"); + checkCommandRegistered("config-get"); + checkCommandRegistered("config-hash-get"); + checkCommandRegistered("config-reload"); + checkCommandRegistered("config-set"); + checkCommandRegistered("config-test"); + checkCommandRegistered("config-write"); + checkCommandRegistered("list-commands"); + checkCommandRegistered("shutdown"); + checkCommandRegistered("status-get"); + checkCommandRegistered("version-get"); + + ctrl->deregisterCommands(); +} + +// Tests that config-write really writes a config file that contains +// Control-agent configuration and not some other random nonsense. +TEST_F(CtrlAgentControllerTest, configWrite) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // First, build the command: + string file = string(TEST_DATA_BUILDDIR) + string("/config-write.json"); + string cmd_txt = "{ \"command\": \"config-write\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params = Element::fromJSON("{\"filename\": \"" + file + "\" }"); + CtrlAgentCommandMgr& mgr_ = CtrlAgentCommandMgr::instance(); + + // Send the command + ConstElementPtr answer = mgr_.handleCommand("config-write", params, cmd); + + // Check that the command was successful + checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS); + + // Now check that the file is there. + ifstream f(file.c_str()); + ASSERT_TRUE(f.good()); + + // Now that's some rough check that the config written really contains + // something that looks like Control-agent configuration. + ConstElementPtr from_file = Element::fromJSONFile(file, true); + ASSERT_TRUE(from_file); + ConstElementPtr ca = from_file->get("Control-agent"); + ASSERT_TRUE(ca); + EXPECT_TRUE(ca->get("control-sockets")); + EXPECT_TRUE(ca->get("hooks-libraries")); + EXPECT_TRUE(ca->get("http-host")); + EXPECT_TRUE(ca->get("http-port")); + + // Remove the file. + ::remove(file.c_str()); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is missing. +TEST_F(CtrlAgentControllerTest, configReloadMissingFile) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + getController()->setConfigFile("does-not-exist.json"); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + // Verify the reload was rejected. + string expected = "{ \"result\": 1, \"text\": " + "\"Configuration parsing failed: " + "Unable to open file does-not-exist.json\" }"; + EXPECT_EQ(expected, answer->str()); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is not a valid JSON. +TEST_F(CtrlAgentControllerTest, configReloadBrokenFile) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + getController()->setConfigFile("testbad.json"); + + // Although Kea is smart, its AI routines are not smart enough to handle + // this one... at least not yet. + ofstream f("testbad.json", ios::trunc); + f << "bla bla bla..."; + f.close(); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + // Verify the reload was rejected. + string expected = "{ \"result\": 1, \"text\": " + "\"Configuration parsing failed: " + "testbad.json:1.1: Invalid character: b\" }"; + EXPECT_EQ(expected, answer->str()); + + // Remove the file. + ::remove("testbad.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is missing. +TEST_F(CtrlAgentControllerTest, configReloadFileValid) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Configuration applied successfully.\" }"; + EXPECT_EQ(expected, answer->str()); + + // Check that the config was indeed applied? + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests that status-get returns expected info (pid, uptime and reload). +TEST_F(CtrlAgentControllerTest, statusGet) { + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, elapsed_time); + + const DControllerBasePtr& ctrl = getController(); + ConstElementPtr response; + ASSERT_NO_THROW(response = ctrl->statusGetHandler("status-get", ConstElementPtr())); + ASSERT_TRUE(response); + ASSERT_EQ(Element::map, response->getType()); + EXPECT_EQ(2, response->size()); + ConstElementPtr result = response->get("result"); + ASSERT_TRUE(result); + ASSERT_EQ(Element::integer, result->getType()); + EXPECT_EQ(0, result->intValue()); + ConstElementPtr arguments = response->get("arguments"); + ASSERT_EQ(Element::map, arguments->getType()); + + // The returned pid should be the pid of our process. + auto found_pid = arguments->get("pid"); + ASSERT_TRUE(found_pid); + EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue()); + + // It is hard to check the actual uptime (and reload) as it is based + // on current time. Let's just make sure it is within a reasonable + // range. + auto found_uptime = arguments->get("uptime"); + ASSERT_TRUE(found_uptime); + EXPECT_LE(found_uptime->intValue(), 5); + EXPECT_GE(found_uptime->intValue(), 0); + + auto found_reload = arguments->get("reload"); + ASSERT_TRUE(found_reload); + EXPECT_LE(found_reload->intValue(), 5); + EXPECT_GE(found_reload->intValue(), 0); +} + +TEST_F(CtrlAgentControllerTest, shutdown) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + + ConstElementPtr cmd = Element::fromJSON("{ \"command\": \"shutdown\"}"); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("shutdown", + params, cmd); + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Control Agent is shutting down\" }"; + + EXPECT_EQ(expected, answer->str()); + + int exit_value = ctrl->getExitValue(); + EXPECT_EQ(0, exit_value); + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + + +TEST_F(CtrlAgentControllerTest, shutdownExitValue) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // This is normally set to whatever value is passed to -c when the server is + // started, but we're not starting it that way, so need to set it by hand. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + + ConstElementPtr cmd = Element::fromJSON("{ \"command\": \"shutdown\"}"); + ConstElementPtr params = Element::fromJSON("{ \"exit-value\": 77 }"); + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("shutdown", + params, cmd); + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Control Agent is shutting down\" }"; + + EXPECT_EQ(expected, answer->str()); + + int exit_value = ctrl->getExitValue(); + EXPECT_EQ(77, exit_value); + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +} |