#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>
#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>
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;
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);
#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>
#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>
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;
};
/// @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;
}
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) {
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.
::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) {
#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>
#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>
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;
};
/// @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 "*").
// 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();
// 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.
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();
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.
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
#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>
#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>
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;
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);
#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>
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;
}
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) {
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.
::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) {
#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>
#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>
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;
// 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();
// 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.
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();
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.
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
'dhcp-forensic-log-tests',
dhcp_forensic_log_tests,
protocol: 'gtest',
+ is_parallel: false,
+ priority: -1,
timeout: 60,
)
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));
std::string file6 = initLeaseFilePath(V6);
if (!file6.empty()) {
conversion_needed = loadLeasesFromFiles<Lease6,
- CSVLeaseFile6>(file6,
+ CSVLeaseFile6>(V6, file6,
lease_file6_,
storage6_);
buildExtendedInfoTables6();
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) {
std::string
Memfile_LeaseMgr::getDefaultLeaseFilePath(Universe u,
- std::string filename /* = "" */) const {
+ std::string filename /* = "" */) {
std::ostringstream s;;
s << CfgMgr::instance().getDataDir();
if (filename.empty()) {
lease_file = conn_.getParameter("name");
} catch (const Exception&) {
// Not specified, use the default.
- return (getDefaultLeaseFilePath(u));
+ return (Memfile_LeaseMgr::getDefaultLeaseFilePath(u));
}
try {
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
// 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");
}
} 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);
// 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();
/// 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.
///
/// @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.
///
/// @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.
/// @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);
// 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