Commit 9e0dc746 authored by Francis Dupont's avatar Francis Dupont

[30-implement-control-socket-for-ddns-2] Added config-set support

parent 3db17a8c
...@@ -113,7 +113,7 @@ using namespace std; ...@@ -113,7 +113,7 @@ using namespace std;
// is parsed. // is parsed.
start: START_JSON { ctx.ctx_ = ctx.NO_KEYWORDS; } json start: START_JSON { ctx.ctx_ = ctx.NO_KEYWORDS; } json
| START_AGENT { ctx.ctx_ = ctx.CONFIG; } agent_syntax_map | START_AGENT { ctx.ctx_ = ctx.CONFIG; } agent_syntax_map
| START_SUB_AGENT { ctx.ctx_ = ctx.AGENT; } sub_agent | START_SUB_AGENT { ctx.ctx_ = ctx.AGENT; } sub_agent
; ;
// This rule defines a "shortcut". Instead of specifying the whole structure // This rule defines a "shortcut". Instead of specifying the whole structure
......
...@@ -57,6 +57,9 @@ CtrlAgentController::registerCommands() { ...@@ -57,6 +57,9 @@ CtrlAgentController::registerCommands() {
CtrlAgentCommandMgr::instance().registerCommand(CONFIG_GET_COMMAND, CtrlAgentCommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
boost::bind(&DControllerBase::configGetHandler, this, _1, _2)); boost::bind(&DControllerBase::configGetHandler, this, _1, _2));
CtrlAgentCommandMgr::instance().registerCommand(CONFIG_SET_COMMAND,
boost::bind(&DControllerBase::configSetHandler, this, _1, _2));
CtrlAgentCommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND, CtrlAgentCommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND,
boost::bind(&DControllerBase::configTestHandler, this, _1, _2)); boost::bind(&DControllerBase::configTestHandler, this, _1, _2));
...@@ -74,6 +77,7 @@ void ...@@ -74,6 +77,7 @@ void
CtrlAgentController::deregisterCommands() { CtrlAgentController::deregisterCommands() {
CtrlAgentCommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND); CtrlAgentCommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND); CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_SET_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND); CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND); CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND); CtrlAgentCommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
......
...@@ -104,7 +104,7 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx, ...@@ -104,7 +104,7 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
} }
// Finally, let's get the hook libs! // Finally, let's get the hook libs!
using namespace isc::hooks; using namespace isc::hooks;
HooksConfig& libraries = ctx->getHooksConfig(); HooksConfig& libraries = ctx->getHooksConfig();
ConstElementPtr hooks = config->get("hooks-libraries"); ConstElementPtr hooks = config->get("hooks-libraries");
......
...@@ -448,6 +448,7 @@ TEST_F(CtrlAgentControllerTest, registeredCommands) { ...@@ -448,6 +448,7 @@ TEST_F(CtrlAgentControllerTest, registeredCommands) {
// Check that the following command are really available. // Check that the following command are really available.
checkCommandRegistered("build-report"); checkCommandRegistered("build-report");
checkCommandRegistered("config-get"); checkCommandRegistered("config-get");
checkCommandRegistered("config-set");
checkCommandRegistered("config-test"); checkCommandRegistered("config-test");
checkCommandRegistered("config-write"); checkCommandRegistered("config-write");
checkCommandRegistered("list-commands"); checkCommandRegistered("list-commands");
......
...@@ -63,7 +63,7 @@ TEST(CtrlAgentProcess, construction) { ...@@ -63,7 +63,7 @@ TEST(CtrlAgentProcess, construction) {
} }
// Verifies that en external call to shutdown causes the run method to // Verifies that en external call to shutdown causes the run method to
// exit gracefully. // exit gracefully.
TEST_F(CtrlAgentProcessTest, shutdown) { TEST_F(CtrlAgentProcessTest, shutdown) {
// Use an asiolink IntervalTimer and callback to generate the // Use an asiolink IntervalTimer and callback to generate the
// shutdown invocation. (Note IntervalTimer setup is in milliseconds). // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
......
...@@ -58,6 +58,9 @@ D2Controller::registerCommands() { ...@@ -58,6 +58,9 @@ D2Controller::registerCommands() {
CommandMgr::instance().registerCommand(CONFIG_GET_COMMAND, CommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
boost::bind(&D2Controller::configGetHandler, this, _1, _2)); boost::bind(&D2Controller::configGetHandler, this, _1, _2));
CommandMgr::instance().registerCommand(CONFIG_SET_COMMAND,
boost::bind(&D2Controller::configSetHandler, this, _1, _2));
CommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND, CommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND,
boost::bind(&D2Controller::configTestHandler, this, _1, _2)); boost::bind(&D2Controller::configTestHandler, this, _1, _2));
...@@ -80,6 +83,7 @@ D2Controller::deregisterCommands() { ...@@ -80,6 +83,7 @@ D2Controller::deregisterCommands() {
// Deregister any registered commands (please keep in alphabetic order) // Deregister any registered commands (please keep in alphabetic order)
CommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND); CommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND); CommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_SET_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND); CommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND); CommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
CommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND); CommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
......
...@@ -722,7 +722,8 @@ TEST_F(CtrlChannelD2Test, configTest) { ...@@ -722,7 +722,8 @@ TEST_F(CtrlChannelD2Test, configTest) {
EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }", EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
response); response);
// Check that the config was not lost. // Check that the config was not lost (fix: reacquire the context).
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys(); keys = d2_context->getKeys();
ASSERT_TRUE(keys); ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size()); EXPECT_EQ(1, keys->size());
...@@ -754,11 +755,145 @@ TEST_F(CtrlChannelD2Test, configTest) { ...@@ -754,11 +755,145 @@ TEST_F(CtrlChannelD2Test, configTest) {
response); response);
// Check that the config was not applied. // Check that the config was not applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys(); keys = d2_context->getKeys();
ASSERT_TRUE(keys); ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size()); EXPECT_EQ(1, keys->size());
} }
// Verify that the "config-set" command will do what we expect.
TEST_F(CtrlChannelD2Test, configSet) {
// Define strings to permutate the config arguments.
// (Note the line feeds makes errors easy to find)
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string d2_header =
" \"DhcpDdns\": \n";
string d2_cfg_txt =
" { \n"
" \"ip-address\": \"192.168.77.1\", \n"
" \"port\": 777, \n"
" \"forward-ddns\" : {}, \n"
" \"reverse-ddns\" : {}, \n"
" \"tsig-keys\": [ \n";
string key1 =
" {\"name\": \"d2_key.example.com\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
string key2 =
" {\"name\": \"d2_key.billcat.net\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"digest-bits\": 120, \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
string bad_key =
" {\"BOGUS\": \"d2_key.example.com\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
string key_footer =
" ] \n";
string control_socket_header =
" ,\"control-socket\": { \n"
" \"socket-type\": \"unix\", \n"
" \"socket-name\": \"";
string control_socket_footer =
"\" \n} \n";
ostringstream os;
// Create a valid config with all the parts should parse.
os << d2_cfg_txt
<< key1
<< key_footer
<< control_socket_header
<< socket_path_
<< control_socket_footer
<< "}\n";
ASSERT_TRUE(server_);
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCPDDNS(os.str(), true));
ASSERT_NO_THROW(d2Controller()->initProcess());
D2ProcessPtr proc = d2Controller()->getProcess();
ASSERT_TRUE(proc);
ConstElementPtr answer = proc->configure(config, false);
ASSERT_TRUE(answer);
EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
answer->str());
ASSERT_NO_THROW(d2Controller()->registerCommands());
// Check that the config was indeed applied.
D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
ASSERT_TRUE(cfg_mgr);
D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
ASSERT_TRUE(d2_context);
TSIGKeyInfoMapPtr keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << config_set_txt << ","
<< args_txt
<< d2_header
<< d2_cfg_txt
<< bad_key
<< key_footer
<< control_socket_header
<< socket_path_
<< control_socket_footer
<< "}\n" // close DhcpDdns.
<< "}}";
// Send the config-set command.
string response;
sendUnixCommand(os.str(), response);
// Should fail with a syntax error.
EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
response);
// Check that the config was not lost (fix: reacquire the context).
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
// Create a valid config with two keys and no command channel.
os.str("");
os << config_set_txt << ","
<< args_txt
<< d2_header
<< d2_cfg_txt
<< key1
<< ",\n"
<< key2
<< key_footer
<< "}\n" // close DhcpDdns.
<< "}}";
// Verify the control channel socket exists.
ASSERT_TRUE(test::fileExists(socket_path_));
// Send the config-set command.
sendUnixCommand(os.str(), response);
// Verify the control channel socket no longer exists.
EXPECT_FALSE(test::fileExists(socket_path_));
// Verify the configuration was successful.
EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
response);
// Check that the config was applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(2, keys->size());
}
// Tests if config-write can be called without any parameters. // Tests if config-write can be called without any parameters.
TEST_F(CtrlChannelD2Test, writeConfigNoFilename) { TEST_F(CtrlChannelD2Test, writeConfigNoFilename) {
EXPECT_NO_THROW(createUnixChannelServer()); EXPECT_NO_THROW(createUnixChannelServer());
......
...@@ -74,6 +74,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set, ...@@ -74,6 +74,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
// so as we can rollback changes when an error occurs. // so as we can rollback changes when an error occurs.
ConfigPtr original_context = context_; ConfigPtr original_context = context_;
resetContext(); resetContext();
bool rollback = false;
// Answer will hold the result returned to the caller. // Answer will hold the result returned to the caller.
ConstElementPtr answer; ConstElementPtr answer;
...@@ -88,12 +89,14 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set, ...@@ -88,12 +89,14 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
// Everything was fine. Configuration set processed successfully. // Everything was fine. Configuration set processed successfully.
if (!check_only) { if (!check_only) {
if (post_config_cb) {
post_config_cb();
}
if (code == 0) { if (code == 0) {
// Call the callback only when parsing was successful.
if (post_config_cb) {
post_config_cb();
}
LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0)); LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
} else {
rollback = true;
} }
// Use the answer provided. // Use the answer provided.
...@@ -107,10 +110,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set, ...@@ -107,10 +110,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what()); LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
answer = isc::config::createAnswer(1, ex.what()); answer = isc::config::createAnswer(1, ex.what());
rollback = true;
// An error occurred, so make sure that we restore original context.
context_ = original_context;
return (answer);
} }
if (check_only) { if (check_only) {
...@@ -119,6 +119,11 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set, ...@@ -119,6 +119,11 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
context_ = original_context; context_ = original_context;
} }
if (rollback) {
// An error occurred, so make sure that we restore original context.
context_ = original_context;
}
return (answer); return (answer);
} }
......
...@@ -540,6 +540,61 @@ DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) { ...@@ -540,6 +540,61 @@ DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
return (checkConfig(module_config)); return (checkConfig(module_config));
} }
ConstElementPtr
DControllerBase::configSetHandler(const std::string&, ConstElementPtr args) {
const int status_code = COMMAND_ERROR; // 1 indicates an error
ConstElementPtr module_config;
std::string app_name = getAppName();
std::string message;
// Command arguments are expected to be:
// { "Module": { ... }, "Logging": { ... } }
// The Logging component is technically optional. If it's not supplied
// logging will revert to default logging.
if (!args) {
message = "Missing mandatory 'arguments' parameter.";
} else {
module_config = args->get(app_name);
if (!module_config) {
message = "Missing mandatory '" + app_name + "' parameter.";
} else if (module_config->getType() != Element::map) {
message = "'" + app_name + "' parameter expected to be a map.";
}
}
if (!message.empty()) {
// Something is amiss with arguments, return a failure response.
ConstElementPtr result = isc::config::createAnswer(status_code,
message);
return (result);
}
// We are starting the configuration process so we should remove any
// staging configuration that has been created during previous
// configuration attempts.
// We're not using cfgmgr to store logging information anymore.
// isc::dhcp::CfgMgr::instance().rollback();
// Temporary storage for logging configuration
ConfigPtr storage = process_->getCfgMgr()->getContext();
// Get 'Logging' element from the config and use it to set up
// logging. If there's no such element, we'll just pass NULL.
Daemon::configureLogger(args->get("Logging"), storage);
// Now we check the server proper.
ConstElementPtr answer = updateConfig(module_config);
int rcode = 0;
parseAnswer(rcode, answer);
if (!rcode) {
// Configuration successful, so apply the logging configuration
// to log4cplus.
storage->applyLoggingCfg();
}
return (answer);
}
ConstElementPtr ConstElementPtr
DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) { DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
ConstElementPtr answer; ConstElementPtr answer;
......
...@@ -297,6 +297,18 @@ public: ...@@ -297,6 +297,18 @@ public:
configTestHandler(const std::string& command, configTestHandler(const std::string& command,
isc::data::ConstElementPtr args); isc::data::ConstElementPtr args);
/// @brief handler for config-set command
///
/// This method handles the config-set command, which checks
/// configuration specified in args parameter.
///
/// @param command (ignored)
/// @param args configuration to be checked.
/// @return status of the command
isc::data::ConstElementPtr
configSetHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief handler for 'shutdown' command /// @brief handler for 'shutdown' command
/// ///
/// This method handles shutdown command. It initiates the shutdown procedure /// This method handles shutdown command. It initiates the shutdown procedure
......
...@@ -40,6 +40,9 @@ static const std::string CONFIG_WRITE_COMMAND("config-write"); ...@@ -40,6 +40,9 @@ static const std::string CONFIG_WRITE_COMMAND("config-write");
/// @brief String value for the config-test command. /// @brief String value for the config-test command.
static const std::string CONFIG_TEST_COMMAND("config-test"); static const std::string CONFIG_TEST_COMMAND("config-test");
/// @brief String value for the config-set command.
static const std::string CONFIG_SET_COMMAND("config-set");
/// @brief String value for the shutdown command. /// @brief String value for the shutdown command.
static const std::string SHUT_DOWN_COMMAND("shutdown"); static const std::string SHUT_DOWN_COMMAND("shutdown");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment