]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[30-implement-control-socket-for-ddns-2] Added config-set support
authorFrancis Dupont <fdupont@isc.org>
Fri, 28 Dec 2018 23:17:35 +0000 (00:17 +0100)
committerFrancis Dupont <fdupont@isc.org>
Thu, 3 Jan 2019 09:05:03 +0000 (04:05 -0500)
src/bin/agent/agent_parser.yy
src/bin/agent/ca_controller.cc
src/bin/agent/simple_parser.cc
src/bin/agent/tests/ca_controller_unittests.cc
src/bin/agent/tests/ca_process_unittests.cc
src/bin/d2/d2_controller.cc
src/bin/d2/tests/d2_command_unittest.cc
src/lib/process/d_cfg_mgr.cc
src/lib/process/d_controller.cc
src/lib/process/d_controller.h
src/lib/process/d_process.h

index f02ee1bf90a0e26cfe69f71778fce7f77f8550a9..fe590f7fea7f685f9a4ef42bd3b1d5d4a5b17320 100644 (file)
@@ -113,7 +113,7 @@ using namespace std;
 // 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
index 0c101b7ad6b3c8a86ac97a07f0285cec53197e16..4f38136ca312853c12f9b8b7204a7196d1efb8bc 100644 (file)
@@ -57,6 +57,9 @@ CtrlAgentController::registerCommands() {
     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));
 
@@ -74,6 +77,7 @@ void
 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);
index 807b7c7d52717c5eb2da2dbfa5b289689c99cbd1..9f8718c7ba1be6194ff990052dab7754b3267e41 100644 (file)
@@ -104,7 +104,7 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
     }
 
     // Finally, let's get the hook libs!
-    
+
     using namespace isc::hooks;
     HooksConfig& libraries = ctx->getHooksConfig();
     ConstElementPtr hooks = config->get("hooks-libraries");
index 636c064300249c2ca2259e0d1a67c652d6cb3238..b2b5d1581c8cf10ef2a58bfcc4bd411cf6aef707 100644 (file)
@@ -448,6 +448,7 @@ TEST_F(CtrlAgentControllerTest, registeredCommands) {
     // 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");
index a6abb47197d96afb0905f862c19dfc828d421617..3f49dfcbdc2d464b91a571ec8501952f5d17334a 100644 (file)
@@ -63,7 +63,7 @@ TEST(CtrlAgentProcess, construction) {
 }
 
 // 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).
index 7e3bb084aecfae3d99d1b81a3f5fdc6966eafbc0..a2e2d891566c42708d9fd84b85846a0bf97cd527 100644 (file)
@@ -58,6 +58,9 @@ D2Controller::registerCommands() {
     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));
 
@@ -80,6 +83,7 @@ D2Controller::deregisterCommands() {
         // 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);
index 4c7675516bfb82d22779579e0f22d5d553482566..cb998b47ba78a53a69da7fc5f3c40c326d94c50e 100644 (file)
@@ -722,7 +722,8 @@ TEST_F(CtrlChannelD2Test, configTest) {
     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());
@@ -754,11 +755,145 @@ TEST_F(CtrlChannelD2Test, configTest) {
               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());
index 800b7751bd69a9fc62553caa20088194253bacc7..21704f0f25b02958dc27d5d87070f7a22b2eda56 100644 (file)
@@ -74,6 +74,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
     // 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;
@@ -88,12 +89,14 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
 
         // 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.
@@ -107,10 +110,7 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
     } 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) {
@@ -119,6 +119,11 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
         context_ = original_context;
     }
 
+    if (rollback) {
+        // An error occurred, so make sure that we restore original context.
+        context_ = original_context;
+    }
+
     return (answer);
 }
 
index 02c4d909635ba25980da3122e04528d515dbe5ec..49fb6db336e2bb19b5c952acb496a3aa2772a3fc 100644 (file)
@@ -540,6 +540,61 @@ DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
     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;
index 34727508c818c457f064570f8d2591c37ae0b6d6..ed4281b865efc24d9bca197087db42e7f9c47342 100644 (file)
@@ -297,6 +297,18 @@ public:
     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
index 62b54703a0b77653b4f1895d5516c931671def86..f3af1b349ddf7116a2427d2083a5b0aa122edb52 100644 (file)
@@ -40,6 +40,9 @@ static const std::string CONFIG_WRITE_COMMAND("config-write");
 /// @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");