// is parsed.
start: START_JSON { ctx.ctx_ = ctx.NO_KEYWORDS; } json
| 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
CtrlAgentCommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
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,
boost::bind(&DControllerBase::configTestHandler, this, _1, _2));
CtrlAgentController::deregisterCommands() {
CtrlAgentCommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
+ CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_SET_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
}
// Finally, let's get the hook libs!
-
+
using namespace isc::hooks;
HooksConfig& libraries = ctx->getHooksConfig();
ConstElementPtr hooks = config->get("hooks-libraries");
// Check that the following command are really available.
checkCommandRegistered("build-report");
checkCommandRegistered("config-get");
+ checkCommandRegistered("config-set");
checkCommandRegistered("config-test");
checkCommandRegistered("config-write");
checkCommandRegistered("list-commands");
}
// Verifies that en external call to shutdown causes the run method to
-// exit gracefully.
+// exit gracefully.
TEST_F(CtrlAgentProcessTest, shutdown) {
// Use an asiolink IntervalTimer and callback to generate the
// shutdown invocation. (Note IntervalTimer setup is in milliseconds).
CommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
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,
boost::bind(&D2Controller::configTestHandler, this, _1, _2));
// Deregister any registered commands (please keep in alphabetic order)
CommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_SET_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
CommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
CommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
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();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
response);
// Check that the config was not applied.
+ d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
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.
TEST_F(CtrlChannelD2Test, writeConfigNoFilename) {
EXPECT_NO_THROW(createUnixChannelServer());
// so as we can rollback changes when an error occurs.
ConfigPtr original_context = context_;
resetContext();
+ bool rollback = false;
// Answer will hold the result returned to the caller.
ConstElementPtr answer;
// Everything was fine. Configuration set processed successfully.
if (!check_only) {
- if (post_config_cb) {
- post_config_cb();
- }
-
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));
+ } else {
+ rollback = true;
}
// Use the answer provided.
} catch (const std::exception& ex) {
LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
answer = isc::config::createAnswer(1, ex.what());
-
- // An error occurred, so make sure that we restore original context.
- context_ = original_context;
- return (answer);
+ rollback = true;
}
if (check_only) {
context_ = original_context;
}
+ if (rollback) {
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ }
+
return (answer);
}
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
DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
ConstElementPtr answer;
configTestHandler(const std::string& command,
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
///
/// This method handles shutdown command. It initiates the shutdown procedure
/// @brief String value for the config-test command.
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.
static const std::string SHUT_DOWN_COMMAND("shutdown");