]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3986] check if LFC is running on config-set
authorRazvan Becheriu <razvan@isc.org>
Thu, 28 Aug 2025 08:48:14 +0000 (11:48 +0300)
committerRazvan Becheriu <razvan@isc.org>
Fri, 29 Aug 2025 11:10:01 +0000 (11:10 +0000)
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/http_control_socket_unittest.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/bin/dhcp6/tests/http_control_socket_unittest.cc
src/hooks/dhcp/forensic_log/tests/meson.build
src/lib/dhcpsrv/memfile_lease_mgr.cc
src/lib/dhcpsrv/memfile_lease_mgr.h
src/lib/util/pid_file.cc

index f62a41f0be06a0e1f83c9a6c953ec050a2642cdc..1d59e0b7ac0bf097e1754f0e76205f11497e9de3 100644 (file)
@@ -14,6 +14,7 @@
 #include <config/http_command_mgr.h>
 #include <config/unix_command_mgr.h>
 #include <cryptolink/crypto_hash.h>
+#include <database/dbaccess_parser.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
@@ -26,6 +27,7 @@
 #include <dhcpsrv/db_type.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <hooks/hooks.h>
 #include <hooks/hooks_manager.h>
 #include <process/cfgrpt/config_report.h>
@@ -374,6 +376,21 @@ ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
         return (result);
     }
 
+    ConstElementPtr lease_database = dhcp4->get("lease-database");
+    if (lease_database) {
+        db::DbAccessParser parser;
+        std::string access_string;
+        parser.parse(access_string, lease_database);
+        auto params = parser.getDbAccessParameters();
+        if (params["type"] == "memfile") {
+            string file_name = params["name"];
+            if (Memfile_LeaseMgr::isLFCProcessRunning(file_name, Memfile_LeaseMgr::V4)) {
+                return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+                        "Can not update configuration while lease file cleanup process is running."));
+            }
+        }
+    }
+
     // stop thread pool (if running)
     MultiThreadingCriticalSection cs;
 
@@ -396,7 +413,7 @@ ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
     ConstElementPtr result = processConfig(dhcp4);
 
     // If the configuration parsed successfully, apply the new logger
-    // configuration and the commit the new configuration.  We apply
+    // configuration and then commit the new configuration.  We apply
     // the logging first in case there's a configuration failure.
     int rcode = 0;
     isc::config::parseAnswer(rcode, result);
index d0f717b9969ed812ef77a1f841e1fd9726c95f9d..ab0800d145f3e9989bc589419cbb89571cbb23ec 100644 (file)
@@ -10,8 +10,9 @@
 #include <asiolink/io_service.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
-#include <config/unix_command_mgr.h>
 #include <config/timeouts.h>
+#include <database/database_connection.h>
+#include <config/unix_command_mgr.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/testutils/iface_mgr_test_config.h>
@@ -19,6 +20,7 @@
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <hooks/hooks_manager.h>
@@ -49,6 +51,7 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
+using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
@@ -102,7 +105,7 @@ public:
 };
 
 /// @brief Fixture class intended for testing control channel in the DHCPv4Srv
-class CtrlChannelDhcpv4SrvTest : public ::testing::Test {
+class CtrlChannelDhcpv4SrvTest : public BaseServerTest {
 public:
     isc::test::Sandbox sandbox;
 
@@ -457,6 +460,18 @@ public:
         }
         return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
     }
+
+    /// @brief Return path to the lease file used by unit tests.
+    ///
+    /// @param filename Name of the lease file appended to the path to the
+    /// directory where test data is held.
+    ///
+    /// @return Full path to the lease file.
+    static std::string getLeaseFilePath(const std::string& filename) {
+        std::ostringstream s;
+        s << CfgMgr::instance().getDataDir() << "/" << filename;
+        return (s.str());
+    }
 };
 
 TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
@@ -875,6 +890,158 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(CtrlChannelDhcpv4SrvTest, configSetLFCRunning) {
+    setLogTestPath("/dev");
+    createUnixChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"192.2.1.0/24\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"192.2.2.0/24\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "       ,\"control-socket\": { \n"
+        "       \"socket-type\": \"unix\", \n"
+        "       \"socket-name\": \"";
+    string control_socket_footer =
+        "\"   \n} \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << set_config_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << socket_path_
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp4
+        << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendUnixCommand(os.str(), response);
+
+    // Verify the configuration was successful. The config contains random
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
+    // hash will be different each time. As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("kea-leases4.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendUnixCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("{ \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" }",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the control channel socket exists.
+    ASSERT_TRUE(fileExists(socket_path_));
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Tests if the server returns its configuration using config-get.
 // Note there are separate tests that verify if toElement() called by the
 // config-get handler are actually converting the configuration correctly.
@@ -1642,6 +1809,59 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configReloadBrokenFile) {
     ::remove("test7.json");
 }
 
+// Tests if config-reload attempts to reload a file while LFC is running
+TEST_F(CtrlChannelDhcpv4SrvTest, configReloadLFCRunning) {
+    createUnixChannelServer();
+    std::string response;
+
+    // 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.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp4\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet4\": ["
+        "        { \"id\": 1, \"subnet\": \"192.0.2.0/24\" },"
+        "        { \"id\": 2, \"subnet\": \"192.0.3.0/24\" }"
+        "     ],"
+        "    \"valid-lifetime\": 4000,"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("kea-leases4.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Now tell Kea to reload its config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed: configuration error using file 'test8.json': "
+              "Can not update configuration while lease file cleanup process is running.\" }",
+              response);
+
+    ::remove("test8.json");
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
 TEST_F(CtrlChannelDhcpv4SrvTest, configReloadValid) {
index 67126f872699b784d97a7e9354777d4b55b46d55..947482be549ea439f39b1df7ab268ff63df63b53 100644 (file)
@@ -13,6 +13,7 @@
 #include <config/command_mgr.h>
 #include <config/http_command_mgr.h>
 #include <config/timeouts.h>
+#include <database/database_connection.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/testutils/iface_mgr_test_config.h>
@@ -20,6 +21,7 @@
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <hooks/hooks_manager.h>
@@ -52,6 +54,7 @@ using namespace isc::asiolink;
 using namespace isc::asiolink::test;
 using namespace isc::config;
 using namespace isc::data;
+using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
@@ -85,7 +88,7 @@ public:
 };
 
 /// @brief Base fixture class intended for testing HTTP/HTTPS control channel.
-class BaseCtrlChannelDhcpv4Test : public ::testing::Test {
+class BaseCtrlChannelDhcpv4Test : public BaseServerTest {
 public:
 
     /// @brief List of interfaces (defaults to "*").
@@ -529,6 +532,9 @@ public:
     // file is not a valid JSON.
     void testConfigReloadBrokenFile();
 
+    // Tests if config-reload attempts to reload a file while LFC is running.
+    void testConfigReloadLFCRunning();
+
     // Tests if config-reload attempts to reload a file and reports that the
     // file is loaded correctly.
     void testConfigReloadValid();
@@ -575,6 +581,18 @@ public:
     // This test verifies that the server signals timeout if the transmission
     // takes too long, having received no data from the client.
     void testConnectionTimeoutNoData();
+
+    /// @brief Return path to the lease file used by unit tests.
+    ///
+    /// @param filename Name of the lease file appended to the path to the
+    /// directory where test data is held.
+    ///
+    /// @return Full path to the lease file.
+    static std::string getLeaseFilePath(const std::string& filename) {
+        std::ostringstream s;
+        s << CfgMgr::instance().getDataDir() << "/" << filename;
+        return (s.str());
+    }
 };
 
 /// @brief Fixture class intended for testing HTTP control channel.
@@ -1179,6 +1197,152 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(HttpCtrlChannelDhcpv4Test, configSetLFCRunning) {
+    createHttpChannelServer();
+
+    // 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 dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"192.2.1.0/24\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"192.2.2.0/24\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"CEA228E014102D623F40E6E9C68EC166A6021A81E220A15276F8C87F68866FDF\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("kea-leases4.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Check that the "config-set" command will replace current configuration.
 TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
     createHttpChannelServer();
@@ -1364,6 +1528,161 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(HttpsCtrlChannelDhcpv4Test, configSetLFCRunning) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"192.2.1.0/24\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"192.2.2.0/24\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124, \n";
+    string control_socket_footer =
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), so the hash will be different each time.
+    // As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("kea-leases4.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Tests if the server returns its configuration using config-get.
 // Note there are separate tests that verify if toElement() called by the
 // config-get handler are actually converting the configuration correctly.
@@ -2508,6 +2827,68 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configReloadBrokenFile) {
     testConfigReloadBrokenFile();
 }
 
+// Tests if config-reload attempts to reload a file while LFC is running.
+void
+BaseCtrlChannelDhcpv4Test::testConfigReloadLFCRunning() {
+    createHttpChannelServer();
+    std::string response;
+
+    // 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.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp4\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet4\": ["
+        "        { \"id\": 1, \"subnet\": \"192.0.2.0/24\" },"
+        "        { \"id\": 2, \"subnet\": \"192.0.3.0/24\" }"
+        "     ],"
+        "    \"valid-lifetime\": 4000,"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("kea-leases4.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+
+    // This command should reload test8.json config.
+    sendHttpCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("[ { \"result\": 1, \"text\": \"Config reload failed: configuration error using file 'test8.json': "
+              "Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    ::remove("test8.json");
+}
+
+TEST_F(HttpCtrlChannelDhcpv4Test, configReloadLFCRunning) {
+    testConfigReloadLFCRunning();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv4Test, configReloadLFCRunning) {
+    testConfigReloadLFCRunning();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
 void
index 3ff5f0e53e3a7a97400dc981b6e2fe607702026a..9d2dcdf6e96449361df6d94ed06d149f64844e1b 100644 (file)
@@ -14,6 +14,7 @@
 #include <config/http_command_mgr.h>
 #include <config/unix_command_mgr.h>
 #include <cryptolink/crypto_hash.h>
+#include <database/dbaccess_parser.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
@@ -26,6 +27,7 @@
 #include <dhcpsrv/db_type.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <hooks/hooks.h>
 #include <hooks/hooks_manager.h>
 #include <process/cfgrpt/config_report.h>
@@ -377,6 +379,21 @@ ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
         return (result);
     }
 
+    ConstElementPtr lease_database = dhcp6->get("lease-database");
+    if (lease_database) {
+        db::DbAccessParser parser;
+        std::string access_string;
+        parser.parse(access_string, lease_database);
+        auto params = parser.getDbAccessParameters();
+        if (params["type"] == "memfile") {
+            string file_name = params["name"];
+            if (Memfile_LeaseMgr::isLFCProcessRunning(file_name, Memfile_LeaseMgr::V6)) {
+                return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+                        "Can not update configuration while lease file cleanup process is running."));
+            }
+        }
+    }
+
     // stop thread pool (if running)
     MultiThreadingCriticalSection cs;
 
@@ -399,7 +416,7 @@ ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
     ConstElementPtr result = processConfig(dhcp6);
 
     // If the configuration parsed successfully, apply the new logger
-    // configuration and the commit the new configuration.  We apply
+    // configuration and then commit the new configuration.  We apply
     // the logging first in case there's a configuration failure.
     int rcode = 0;
     isc::config::parseAnswer(rcode, result);
index 3b9df41e2d20728356911d7b7833a4da02aef3da..d6ca8608c874314f617d8a98ff4f7b97453aa357 100644 (file)
@@ -10,6 +10,7 @@
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
 #include <config/timeouts.h>
+#include <database/database_connection.h>
 #include <config/unix_command_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/testutils/iface_mgr_test_config.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <hooks/hooks_manager.h>
 #include <log/logger_support.h>
+#include <process/log_parser.h>
 #include <stats/stats_mgr.h>
 #include <util/multi_threading_mgr.h>
 #include <util/chrono_time_utils.h>
@@ -49,6 +52,7 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
+using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
@@ -459,6 +463,18 @@ public:
         }
         return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
     }
+
+    /// @brief Return path to the lease file used by unit tests.
+    ///
+    /// @param filename Name of the lease file appended to the path to the
+    /// directory where test data is held.
+    ///
+    /// @return Full path to the lease file.
+    static std::string getLeaseFilePath(const std::string& filename) {
+        std::ostringstream s;
+        s << CfgMgr::instance().getDataDir() << "/" << filename;
+        return (s.str());
+    }
 };
 
 TEST_F(CtrlDhcpv6SrvTest, commands) {
@@ -885,6 +901,159 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(CtrlChannelDhcpv6SrvTest, configSetLFCRunning) {
+    setLogTestPath("/dev");
+    createUnixChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "       ,\"control-socket\": { \n"
+        "       \"socket-type\": \"unix\", \n"
+        "       \"socket-name\": \"";
+    string control_socket_footer =
+        "\"   \n} \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << set_config_txt << ","
+        << args_txt
+        << dhcp6_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << socket_path_
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp6
+        << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendUnixCommand(os.str(), response);
+
+    // Verify the configuration was successful. The config contains random
+    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // hash will be different each time. As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("kea-leases6.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendUnixCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("{ \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" }",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the control channel socket exists.
+    ASSERT_TRUE(fileExists(socket_path_));
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Tests if the server returns its configuration using config-get.
 // Note there are separate tests that verify if toElement() called by the
 // config-get handler are actually converting the configuration correctly.
@@ -1644,6 +1813,58 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configReloadBrokenFile) {
     ::remove("test7.json");
 }
 
+// Tests if config-reload attempts to reload a file while LFC is running
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadLFCRunning) {
+    createUnixChannelServer();
+    std::string response;
+
+    // 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.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp6\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet6\": ["
+        "        { \"subnet\": \"2001:db8:1::/64\", \"id\": 1 },"
+        "        { \"subnet\": \"2001:db8:2::/64\", \"id\": 2 }"
+        "     ],"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("kea-leases6.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Now tell Kea to reload its config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed: configuration error using file 'test8.json': "
+              "Can not update configuration while lease file cleanup process is running.\" }",
+              response);
+
+    ::remove("test8.json");
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
 TEST_F(CtrlChannelDhcpv6SrvTest, configReloadValid) {
index b13634ca3cb2372427c75a987c509ebedb2ba95e..1fc1bfc792a1a806b249c2e2fc544d27624be8b4 100644 (file)
 #include <config/command_mgr.h>
 #include <config/http_command_mgr.h>
 #include <config/timeouts.h>
+#include <database/database_connection.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/testutils/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <hooks/hooks_manager.h>
@@ -26,6 +28,7 @@
 #include <http/response_parser.h>
 #include <http/testutils/test_http_client.h>
 #include <log/logger_support.h>
+#include <process/log_parser.h>
 #include <stats/stats_mgr.h>
 #include <util/chrono_time_utils.h>
 
@@ -52,6 +55,7 @@ using namespace isc::asiolink;
 using namespace isc::asiolink::test;
 using namespace isc::config;
 using namespace isc::data;
+using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
@@ -539,6 +543,9 @@ public:
     // file is not a valid JSON.
     void testConfigReloadBrokenFile();
 
+    // Tests if config-reload attempts to reload a file while LFC is running.
+    void testConfigReloadLFCRunning();
+
     // Tests if config-reload attempts to reload a file and reports that the
     // file is loaded correctly.
     void testConfigReloadValid();
@@ -585,6 +592,18 @@ public:
     // This test verifies that the server signals timeout if the transmission
     // takes too long, having received no data from the client.
     void testConnectionTimeoutNoData();
+
+    /// @brief Return path to the lease file used by unit tests.
+    ///
+    /// @param filename Name of the lease file appended to the path to the
+    /// directory where test data is held.
+    ///
+    /// @return Full path to the lease file.
+    static std::string getLeaseFilePath(const std::string& filename) {
+        std::ostringstream s;
+        s << CfgMgr::instance().getDataDir() << "/" << filename;
+        return (s.str());
+    }
 };
 
 /// @brief Fixture class intended for testing HTTP control channel.
@@ -1196,6 +1215,153 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(HttpCtrlChannelDhcpv6Test, configSetLFCRunning) {
+    createHttpChannelServer();
+
+    // 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 dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"7B1A2256CDB80F66DEBFC9C86D1210717A1F2DB45BEF532C30865FD50ECCDC3D\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("kea-leases6.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Check that the "config-set" command will replace current configuration.
 TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
     createHttpChannelServer();
@@ -1382,6 +1548,162 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
     CfgMgr::instance().clear();
 }
 
+// Check that the "config-set" fails when LFC is running.
+TEST_F(HttpsCtrlChannelDhcpv6Test, configSetLFCRunning) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126, \n";
+    string control_socket_footer =
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), so the hash will be different each time.
+    // As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Create the backend configuration.
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("kea-leases6.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with an error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 // Tests if the server returns its configuration using config-get.
 // Note there are separate tests that verify if toElement() called by the
 // config-get handler are actually converting the configuration correctly.
@@ -2505,6 +2827,67 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadBrokenFile) {
     testConfigReloadBrokenFile();
 }
 
+// Tests if config-reload attempts to reload a file while LFC is running.
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadLFCRunning() {
+    createHttpChannelServer();
+    std::string response;
+
+    // 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.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp6\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"*\" ]"
+        "    },"
+        "    \"subnet6\": ["
+        "        { \"subnet\": \"2001:db8:1::/64\", \"id\": 1 },"
+        "        { \"subnet\": \"2001:db8:2::/64\", \"id\": 2 }"
+        "     ],"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    DatabaseConnection::ParameterMap pmap;
+    pmap["type"] = "memfile";
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("kea-leases6.csv");
+    pmap["lfc-interval"] = "1";
+
+    // Create a pid file holding the PID of the current process. Choosing the
+    // pid of the current process guarantees that when the backend starts up
+    // the process is alive.
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+    pid_file.write();
+
+    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(&pid_file), [](void* p) { reinterpret_cast<PIDFile*>(p)->deleteFile(); });
+
+
+    // This command should reload test8.json config.
+    sendHttpCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the reload will fail.
+    EXPECT_EQ("[ { \"result\": 1, \"text\": \"Config reload failed: configuration error using file 'test8.json': "
+              "Can not update configuration while lease file cleanup process is running.\" } ]",
+              response);
+
+    ::remove("test8.json");
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadLFCRunning) {
+    testConfigReloadLFCRunning();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadLFCRunning) {
+    testConfigReloadLFCRunning();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
 void
index 5f71ab5200df185a17b221ccd671f62b91840edc..35f92feca9765b767d523dc6189b15bef5cec5c2 100644 (file)
@@ -51,5 +51,7 @@ test(
     'dhcp-forensic-log-tests',
     dhcp_forensic_log_tests,
     protocol: 'gtest',
+    is_parallel: false,
+    priority: -1,
     timeout: 60,
 )
index e6096e956460522283f12bfc912ad939c7f2da01..f858b8c788452ca2c45c61254d5b15c948faac10 100644 (file)
@@ -1008,7 +1008,7 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& param
         std::string file4 = initLeaseFilePath(V4);
         if (!file4.empty()) {
             conversion_needed = loadLeasesFromFiles<Lease4,
-                                                 CSVLeaseFile4>(file4,
+                                                 CSVLeaseFile4>(V4, file4,
                                                                 lease_file4_,
                                                                 storage4_);
             static_cast<void>(extractExtendedInfo4(false, false));
@@ -1017,7 +1017,7 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& param
         std::string file6 = initLeaseFilePath(V6);
         if (!file6.empty()) {
             conversion_needed = loadLeasesFromFiles<Lease6,
-                                                 CSVLeaseFile6>(file6,
+                                                 CSVLeaseFile6>(V6, file6,
                                                                 lease_file6_,
                                                                 storage6_);
             buildExtendedInfoTables6();
@@ -2260,6 +2260,16 @@ Memfile_LeaseMgr::rollback() {
               DHCPSRV_MEMFILE_ROLLBACK);
 }
 
+bool
+Memfile_LeaseMgr::isLFCProcessRunning(const std::string file_name, Universe u) {
+    std::string lease_file(file_name);
+    if (lease_file.empty()) {
+        lease_file = Memfile_LeaseMgr::getDefaultLeaseFilePath(u);
+    }
+    PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(lease_file, FILE_PID));
+    return (pid_file.check());
+}
+
 std::string
 Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
                                const LFCFileType& file_type) {
@@ -2290,7 +2300,7 @@ Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
 
 std::string
 Memfile_LeaseMgr::getDefaultLeaseFilePath(Universe u,
-                                          std::string filename /* = "" */) const {
+                                          std::string filename /* = "" */) {
     std::ostringstream s;;
     s << CfgMgr::instance().getDataDir();
     if (filename.empty()) {
@@ -2350,7 +2360,7 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
         lease_file = conn_.getParameter("name");
     } catch (const Exception&) {
         // Not specified, use the default.
-        return (getDefaultLeaseFilePath(u));
+        return (Memfile_LeaseMgr::getDefaultLeaseFilePath(u));
     }
 
     try {
@@ -2365,7 +2375,7 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
 
 template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
 bool
-Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
+Memfile_LeaseMgr::loadLeasesFromFiles(Universe u, const std::string& filename,
                                       boost::shared_ptr<LeaseFileType>& lease_file,
                                       StorageType& storage) {
     // Check if the instance of the LFC is running right now. If it is
@@ -2374,8 +2384,7 @@ Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
     // it should go through.
     /// @todo Consider applying a timeout for an LFC and retry when this
     /// timeout elapses.
-    PIDFile pid_file(appendSuffix(filename, FILE_PID));
-    if (pid_file.check()) {
+    if (Memfile_LeaseMgr::isLFCProcessRunning(filename, u)) {
         isc_throw(DbOpenError, "unable to load leases from files while the "
                   "lease file cleanup is in progress");
     }
@@ -2413,14 +2422,14 @@ Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
     } else {
         // If the leasefile.completed doesn't exist, let's load the leases
         // from leasefile.2 and leasefile.1, if they exist.
-        lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_PREVIOUS)));
+        lease_file.reset(new LeaseFileType(Memfile_LeaseMgr::appendSuffix(filename, FILE_PREVIOUS)));
         if (lease_file->exists()) {
             LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                                    max_row_errors);
             conversion_needed = conversion_needed || lease_file->needsConversion();
         }
 
-        lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
+        lease_file.reset(new LeaseFileType(Memfile_LeaseMgr::appendSuffix(filename, FILE_INPUT)));
         if (lease_file->exists()) {
             LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                                    max_row_errors);
@@ -2500,8 +2509,8 @@ Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) {
     // is an indication that another LFC instance may be in progress or
     // may be stalled. In that case we don't want to rotate the current
     // lease file to avoid overriding the contents of the existing file.
-    CSVFile lease_file_finish(appendSuffix(lease_file->getFilename(), FILE_FINISH));
-    CSVFile lease_file_copy(appendSuffix(lease_file->getFilename(), FILE_INPUT));
+    CSVFile lease_file_finish(Memfile_LeaseMgr::appendSuffix(lease_file->getFilename(), FILE_FINISH));
+    CSVFile lease_file_copy(Memfile_LeaseMgr::appendSuffix(lease_file->getFilename(), FILE_INPUT));
     if (!lease_file_finish.exists() && !lease_file_copy.exists()) {
         // Close the current file so as we can move it to the copy file.
         lease_file->close();
index 42c809380f0b30ac1c54110e1b2563919a4ed385..cd3213ec9bd118bd6ac8d4b6e4636624b78bcb42 100644 (file)
@@ -963,6 +963,11 @@ public:
     /// support transactions, this is a no-op.
     virtual void rollback() override;
 
+    /// @brief Check if LFC is running.
+    ///
+    /// @return True if LFC is running, false otherwise (memfile only).
+    static bool isLFCProcessRunning(const std::string file_name, Universe u);
+
     //@}
 
     /// @name Public type and method used to determine file names for LFC.
@@ -1012,8 +1017,8 @@ public:
     ///
     /// @param u Universe (V4 or V6).
     /// @param filename optional filename to use.
-    std::string getDefaultLeaseFilePath(Universe u,
-                                        const std::string filename = "") const;
+    static std::string getDefaultLeaseFilePath(Universe u,
+                                               const std::string filename = "");
 
     /// @brief Returns an absolute path to the lease file.
     ///
@@ -1099,6 +1104,7 @@ private:
     /// @todo Consider implementing delaying the lease files loading when
     /// the LFC is in progress by the specified amount of time.
     ///
+    /// @param u Universe (V4 or V6).
     /// @param filename Name of the lease file.
     /// @param lease_file An object representing a lease file to which
     /// the server will store lease updates.
@@ -1114,7 +1120,7 @@ private:
     /// @throw DbOpenError when it is found that the LFC is in progress.
     template<typename LeaseObjectType, typename LeaseFileType,
              typename StorageType>
-    bool loadLeasesFromFiles(const std::string& filename,
+    bool loadLeasesFromFiles(Universe u, const std::string& filename,
                              boost::shared_ptr<LeaseFileType>& lease_file,
                              StorageType& storage);
 
index ef519b3d870fdfb42c4f5a01d9fde3a7cbcf3313..89d2681ebcf475932c0f97b0b6ef4be327095fb2 100644 (file)
@@ -31,7 +31,7 @@ PIDFile::check() const {
     // If we weren't able to open the file treat
     // it as if the process wasn't running
     if (!fs.is_open()) {
-        return (false);
+        return (0);
     }
 
     // Try to get the pid, get the status and get rid of the file