#include <asiolink/interval_timer.h>
#include <asiolink/io_address.h>
+#include <asiolink/testutils/test_tls.h>
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
+using namespace isc::asiolink::test;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
};
-class HttpCtrlChannelDhcpv6Test : public HttpCtrlDhcpv6Test {
+/// @brief Base fixture class intended for testing HTTP/HTTPS control channel.
+class BaseCtrlChannelDhcpv6Test : public HttpCtrlDhcpv6Test {
public:
/// @brief List of interfaces (defaults to "*").
std::string interfaces_;
/// @brief Default constructor
///
/// Sets socket path to its default value.
- HttpCtrlChannelDhcpv6Test() : interfaces_("\"*\"") {
+ BaseCtrlChannelDhcpv6Test() : interfaces_("\"*\"") {
reset();
IfaceMgr::instance().setTestMode(false);
IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
}
/// @brief Destructor
- ~HttpCtrlChannelDhcpv6Test() {
+ virtual ~BaseCtrlChannelDhcpv6Test() {
LeaseMgrFactory::destroy();
StatsMgr::instance().removeAll();
IOServicePtr io_service = getIOService();
ASSERT_TRUE(io_service);
IntervalTimer test_timer(io_service);
- test_timer.setup(std::bind(&HttpCtrlChannelDhcpv6Test::timeoutHandler,
+ test_timer.setup(std::bind(&BaseCtrlChannelDhcpv6Test::timeoutHandler,
this, true),
TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
// Run until the client stops the service or an error occurs.
}
/// @brief Create a server with a HTTP command channel.
- void createHttpChannelServer() {
- // Just a simple config. The important part here is the socket
- // location information.
- std::string header =
- "{"
- " \"interfaces-config\": {"
- " \"interfaces\": [";
-
- std::string body = "]"
- " },"
- " \"expired-leases-processing\": {"
- " \"reclaim-timer-wait-time\": 60,"
- " \"hold-reclaimed-time\": 500,"
- " \"flush-reclaimed-timer-wait-time\": 60"
- " },"
- " \"rebind-timer\": 2000, "
- " \"renew-timer\": 1000, "
- " \"subnet6\": [ ],"
- " \"valid-lifetime\": 4000,"
- " \"control-socket\": {"
- " \"socket-type\": \"http\","
- " \"socket-address\": \"::1\","
- " \"socket-port\": 18126"
- " },"
- " \"lease-database\": {"
- " \"type\": \"memfile\", \"persist\": false },"
- " \"loggers\": [ {"
- " \"name\": \"kea-dhcp6\","
- " \"severity\": \"INFO\","
- " \"debuglevel\": 0"
- " } ]"
- "}";
-
- std::string config_txt = header + interfaces_ + body;
- ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
-
- ConstElementPtr config;
- ASSERT_NO_THROW(config = parseDHCP6(config_txt));
-
- // Parse the logger configuration explicitly into the staging config.
- // Note this does not alter the current loggers, they remain in
- // effect until we apply the logging config below. If no logging
- // is supplied logging will revert to default logging.
- server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
-
- // Let's apply the new logging. We do it early, so we'll be able to print
- // out what exactly is wrong with the new config in case of problems.
- CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
-
- ConstElementPtr answer = server_->processConfig(config);
-
- // Commit the configuration so any subsequent reconfigurations
- // will only close the command channel if its configuration has
- // changed.
- CfgMgr::instance().commit();
-
- ASSERT_TRUE(answer);
-
- int status = 0;
- ConstElementPtr txt = isc::config::parseAnswer(status, answer);
- // This should succeed. If not, print the error message.
- ASSERT_EQ(0, status) << txt->str();
-
- // Now check that the socket was indeed open.
- ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
- }
+ virtual void createHttpChannelServer() = 0;
+ /// @brief Create a server with a HTTP command channel.
/// @brief Reset
void reset() override {
HttpCtrlDhcpv6Test::reset();
/// @param command the command text to execute in JSON form.
/// @param response variable into which the received response should be
/// placed.
- void sendHttpCommand(const std::string& command, std::string& response) {
- response = "";
- IOServicePtr io_service = getIOService();
- ASSERT_TRUE(io_service);
- boost::scoped_ptr<TestHttpClient> client;
- client.reset(new TestHttpClient(io_service, SERVER_ADDRESS,
- SERVER_PORT));
- ASSERT_TRUE(client);
-
- // Send the command. This will trigger server's handler which receives
- // data over the HTTP socket. The server will start sending response
- // to the client.
- ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
- runIOService();
- ASSERT_TRUE(client->receiveDone());
-
- // Read the response generated by the server.
- HttpResponsePtr hr;
- ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
- response = hr->getBody();
-
- // Now close client.
- client->close();
-
- ASSERT_NO_THROW(io_service->poll());
- }
+ virtual void sendHttpCommand(const std::string& command,
+ std::string& response) = 0;
/// @brief Parse list answer.
///
}
return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
}
-};
-// Tests that the server properly responds to invalid commands sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelNegative) {
- createHttpChannelServer();
- std::string response;
+ // Tests that the server properly responds to invalid commands sent
+ // via ControlChannel.
+ void testControlChannelNegative();
- sendHttpCommand("{ \"command\": \"bogus\" }", response);
- EXPECT_EQ("[ { \"result\": 2,"
- " \"text\": \"'bogus' command not supported.\" } ]",
- response);
+ // Tests that the server properly responds to shutdown command sent
+ // via ControlChannel.
+ void testControlChannelShutdown();
- sendHttpCommand("utter nonsense", response);
- EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
-}
+ // Tests that the server properly responds to statistics commands.
+ void testControlChannelStats();
-// Tests that the server properly responds to shutdown command sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelShutdown) {
- createHttpChannelServer();
- std::string response;
+ // Tests if the server returns its configuration using config-get.
+ void testConfigGet();
- sendHttpCommand("{ \"command\": \"shutdown\" }", response);
- EXPECT_EQ("[ { \"result\": 0, \"text\": \"Shutting down.\" } ]", response);
+ // Tests if the server returns the hash of its configuration using
+ // config-hash-get.
+ void testConfigHashGet();
- EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
-}
+ // This test verifies that the DHCP server handles version-get commands.
+ void testGetVersion();
-// Check that the "config-set" command will replace current configuration
-TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
- createHttpChannelServer();
+ // This test verifies that the DHCP server handles server-tag-get command.
+ void testServerTagGet();
- // 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 =
- " ,\"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";
+ // This test verifies that the DHCP server handles status-get commands.
+ void testStatusGet();
- std::ostringstream os;
+ // Check that status is returned even if LeaseMgr and HostMgr are
+ // not created.
+ void testNoManagers();
- // 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
- << logger_txt
- << "}\n" // close dhcp6
- << "}}";
+ // Checks that socket status exists in status-get responses.
+ void testStatusGetSockets();
- // Send the config-set command
- std::string response;
- sendHttpCommand(os.str(), response);
- EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
- response);
+ // Checks that socket status includes errors in status-get responses.
+ void testStatusGetSocketsErrors();
- // Check that the config was indeed applied.
- const Subnet6Collection* subnets =
- CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
- EXPECT_EQ(1, subnets->size());
+ // This test verifies that the DHCP server handles
+ // config-backend-pull command.
+ void testConfigBackendPull();
- OptionDefinitionPtr def =
- LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
- ASSERT_TRUE(def);
+ // This test verifies that the DHCP server immediately reclaims expired
+ // leases on leases-reclaim command.
+ void testControlLeasesReclaim();
- // Create a config with malformed subnet that should fail to parse.
- os.str("");
- os << set_config_txt << ","
- << args_txt
- << dhcp6_cfg_txt
- << bad_subnet
- << subnet_footer
- << control_socket
- << "}\n" // close dhcp6
- << "}}";
+ // This test verifies that the DHCP server immediately reclaims expired
+ // leases on leases-reclaim command with remove = true.
+ void testControlLeasesReclaimRemove();
- // Send the config-set command
- sendHttpCommand(os.str(), response);
+ // Tests that the server properly responds to list-commands command sent
+ // via ControlChannel.
+ void testListCommands();
- // Should fail with a syntax error
- EXPECT_EQ("[ { \"result\": 1, "
- "\"text\": \"subnet configuration failed: mandatory 'subnet' "
- "parameter is missing for a subnet being configured "
- "(<string>:21:17)\" } ]",
- response);
+ // Tests if config-write can be called without any parameters.
+ void testConfigWriteNoFilename();
- // Check that the config was not lost
- subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
- EXPECT_EQ(1, subnets->size());
+ // Tests if config-write can be called with a valid filename as parameter.
+ void testConfigWriteFilename();
- def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
- ASSERT_TRUE(def);
+ // Tests if config-reload attempts to reload a file and reports that the
+ // file is missing.
+ void testConfigReloadMissingFile();
- // Create a valid config with two subnets and no command channel.
- // It should succeed, client should still receive the response
- os.str("");
- os << set_config_txt << ","
- << args_txt
- << dhcp6_cfg_txt
- << subnet1
- << ",\n"
- << subnet2
- << subnet_footer
- << "}\n" // close dhcp6
- << "}}";
+ // Tests if config-reload attempts to reload a file and reports that the
+ // file is not a valid JSON.
+ void testConfigReloadBrokenFile();
- // Verify the HTTP control channel socket exists.
- EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+ // Tests if config-reload attempts to reload a file and reports that the
+ // file is loaded correctly.
+ void testConfigReloadValid();
- // Send the config-set command.
- sendHttpCommand(os.str(), response);
+ // Tests if config-reload attempts to reload a file and reports that the
+ // file is loaded correctly.
+ void testConfigReloadDetectInterfaces();
- // Verify the HTTP control channel socket no longer exists.
- ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
- EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+ // This test verifies that disable DHCP service command performs
+ // sanity check on parameters.
+ void testDhcpDisableBadParam();
- // With no command channel, should still receive the response.
- EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
- response);
+ // This test verifies if it is possible to disable DHCP service
+ // via command.
+ void testDhcpDisable();
- // Check that the config was not lost
- subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
- EXPECT_EQ(2, subnets->size());
+ // This test verifies if it is possible to disable DHCP service using
+ // the origin-id.
+ void testDhcpDisableOriginId();
- // Clean up after the test.
- CfgMgr::instance().clear();
-}
+ // This test verifies that it is possible to disable DHCP service
+ // for a short period of time, after which the service is
+ // automatically enabled.
+ void testDhcpDisableTemporarily();
-// 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.
-TEST_F(HttpCtrlChannelDhcpv6Test, configGet) {
- createHttpChannelServer();
- std::string response;
+ // This test verifies that enable DHCP service command performs
+ // sanity check on parameters.
+ void testDhcpEnableBadParam();
- sendHttpCommand("{ \"command\": \"config-get\" }", response);
- ConstElementPtr rsp;
+ // This test verifies if it is possible to enable DHCP service via command.
+ void testDhcpEnable();
- // The response should be a valid JSON.
- EXPECT_NO_THROW(rsp = Element::fromJSON(response));
- ASSERT_TRUE(rsp);
+ // This test verifies if it is possible to enable DHCP service using
+ // the origin-id.
+ void testDhcpEnableOriginId();
- int status;
- ConstElementPtr cfg = parseListAnswer(status, rsp);
- EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+ // This test verifies that the server can receive and process a
+ // large command.
+ void testLongCommand();
- // Ok, now roughly check if the response seems legit.
- ASSERT_TRUE(cfg);
- ASSERT_EQ(Element::map, cfg->getType());
- EXPECT_TRUE(cfg->get("Dhcp6"));
- EXPECT_TRUE(cfg->get("Dhcp6")->get("loggers"));
-}
+ // This test verifies that the server can send long response to the client.
+ void testLongResponse();
+
+ // This test verifies that the server signals timeout if the transmission
+ // takes too long, having received no data from the client.
+ void testConnectionTimeoutNoData();
+};
+
+/// @brief Fixture class intended for testing HTTP control channel.
+class HttpCtrlChannelDhcpv6Test : public BaseCtrlChannelDhcpv6Test {
+public:
+
+ virtual void createHttpChannelServer() override {
+ // Just a simple config. The important part here is the socket
+ // location information.
+ std::string header =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [";
+
+ std::string body = "]"
+ " },"
+ " \"expired-leases-processing\": {"
+ " \"reclaim-timer-wait-time\": 60,"
+ " \"hold-reclaimed-time\": 500,"
+ " \"flush-reclaimed-timer-wait-time\": 60"
+ " },"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"http\","
+ " \"socket-address\": \"::1\","
+ " \"socket-port\": 18126"
+ " },"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\", \"persist\": false },"
+ " \"loggers\": [ {"
+ " \"name\": \"kea-dhcp6\","
+ " \"severity\": \"INFO\","
+ " \"debuglevel\": 0"
+ " } ]"
+ "}";
+
+ std::string config_txt = header + interfaces_ + body;
+ ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+
+ // Parse the logger configuration explicitly into the staging config.
+ // Note this does not alter the current loggers, they remain in
+ // effect until we apply the logging config below. If no logging
+ // is supplied logging will revert to default logging.
+ server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
+
+ // Let's apply the new logging. We do it early, so we'll be able to print
+ // out what exactly is wrong with the new config in case of problems.
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ ConstElementPtr answer = server_->processConfig(config);
+
+ // Commit the configuration so any subsequent reconfigurations
+ // will only close the command channel if its configuration has
+ // changed.
+ CfgMgr::instance().commit();
+
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ ConstElementPtr txt = isc::config::parseAnswer(status, answer);
+ // This should succeed. If not, print the error message.
+ ASSERT_EQ(0, status) << txt->str();
+
+ // Now check that the socket was indeed open.
+ ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+ }
+
+ /// @brief Conducts a command/response exchange via HttpCommandSocket.
+ ///
+ /// This method connects to the given server over the given address/port.
+ /// If successful, it then sends the given command and retrieves the
+ /// server's response. Note that it polls the server's I/O service
+ /// where needed to cause the server to process IO events on
+ /// the control channel sockets.
+ ///
+ /// @param command the command text to execute in JSON form.
+ /// @param response variable into which the received response should be
+ /// placed.
+ virtual void sendHttpCommand(const std::string& command,
+ std::string& response) override {
+ response = "";
+ IOServicePtr io_service = getIOService();
+ ASSERT_TRUE(io_service);
+ boost::scoped_ptr<TestHttpClient> client;
+ client.reset(new TestHttpClient(io_service, SERVER_ADDRESS,
+ SERVER_PORT));
+ ASSERT_TRUE(client);
+
+ // Send the command. This will trigger server's handler which receives
+ // data over the HTTP socket. The server will start sending response
+ // to the client.
+ ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
+ runIOService();
+ ASSERT_TRUE(client->receiveDone());
+
+ // Read the response generated by the server.
+ HttpResponsePtr hr;
+ ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+ response = hr->getBody();
+
+ // Now close client.
+ client->close();
+
+ ASSERT_NO_THROW(io_service->poll());
+ }
+};
+
+/// @brief Fixture class intended for testing HTTPS control channel.
+class HttpsCtrlChannelDhcpv6Test : public BaseCtrlChannelDhcpv6Test {
+public:
+
+ virtual void createHttpChannelServer() override {
+ // Just a simple config. The important part here is the socket
+ // location information.
+ string ca_dir(string(TEST_CA_DIR));
+ ostringstream config_st;
+ config_st
+ << "{"
+ << " \"interfaces-config\": {"
+ << " \"interfaces\": ["
+ << interfaces_
+ << "]"
+ << " },"
+ << " \"expired-leases-processing\": {"
+ << " \"reclaim-timer-wait-time\": 60,"
+ << " \"hold-reclaimed-time\": 500,"
+ << " \"flush-reclaimed-timer-wait-time\": 60"
+ << " },"
+ << " \"rebind-timer\": 2000, "
+ << " \"renew-timer\": 1000, "
+ << " \"subnet6\": [ ],"
+ << " \"valid-lifetime\": 4000,"
+ << " \"control-socket\": {"
+ << " \"socket-type\": \"http\","
+ << " \"socket-address\": \"::1\","
+ << " \"socket-port\": 18126,"
+ << " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\","
+ << " \"cert-file\": \"" << ca_dir << "/kea-server.crt\","
+ << " \"key-file\": \"" << ca_dir << "/kea-server.key\""
+ << " },"
+ << " \"lease-database\": {"
+ << " \"type\": \"memfile\", \"persist\": false },"
+ << " \"loggers\": [ {"
+ << " \"name\": \"kea-dhcp6\","
+ << " \"severity\": \"INFO\","
+ << " \"debuglevel\": 0"
+ << " } ]"
+ << "}";
+
+ ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_st.str()));
+
+ // Parse the logger configuration explicitly into the staging config.
+ // Note this does not alter the current loggers, they remain in
+ // effect until we apply the logging config below. If no logging
+ // is supplied logging will revert to default logging.
+ server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
+
+ // Let's apply the new logging. We do it early, so we'll be able to print
+ // out what exactly is wrong with the new config in case of problems.
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ ConstElementPtr answer = server_->processConfig(config);
+
+ // Commit the configuration so any subsequent reconfigurations
+ // will only close the command channel if its configuration has
+ // changed.
+ CfgMgr::instance().commit();
+
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ ConstElementPtr txt = isc::config::parseAnswer(status, answer);
+ // This should succeed. If not, print the error message.
+ ASSERT_EQ(0, status) << txt->str();
+
+ // Now check that the socket was indeed open.
+ ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+ }
+
+ /// @brief Conducts a command/response exchange via HttpCommandSocket.
+ ///
+ /// This method connects to the given server over the given address/port.
+ /// If successful, it then sends the given command and retrieves the
+ /// server's response. Note that it polls the server's I/O service
+ /// where needed to cause the server to process IO events on
+ /// the control channel sockets.
+ ///
+ /// @param command the command text to execute in JSON form.
+ /// @param response variable into which the received response should be
+ /// placed.
+ virtual void sendHttpCommand(const std::string& command,
+ std::string& response) override {
+ response = "";
+ IOServicePtr io_service = getIOService();
+ ASSERT_TRUE(io_service);
+ boost::scoped_ptr<TestHttpsClient> client;
+ TlsContextPtr client_tls_context;
+ configClient(client_tls_context);
+ client.reset(new TestHttpsClient(io_service, client_tls_context,
+ SERVER_ADDRESS, SERVER_PORT));
+ ASSERT_TRUE(client);
+
+ // Send the command. This will trigger server's handler which receives
+ // data over the HTTP socket. The server will start sending response
+ // to the client.
+ ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
+ runIOService();
+ ASSERT_TRUE(client->receiveDone());
+
+ // Read the response generated by the server.
+ HttpResponsePtr hr;
+ ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+ response = hr->getBody();
+
+ // Now close client.
+ client->close();
+
+ ASSERT_NO_THROW(io_service->poll());
+ }
+};
+
+// Tests that the server properly responds to invalid commands sent
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelNegative() {
+ createHttpChannelServer();
+ std::string response;
+
+ sendHttpCommand("{ \"command\": \"bogus\" }", response);
+ EXPECT_EQ("[ { \"result\": 2,"
+ " \"text\": \"'bogus' command not supported.\" } ]",
+ response);
+
+ sendHttpCommand("utter nonsense", response);
+ EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelNegative) {
+ testControlChannelNegative();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelNegative) {
+ testControlChannelNegative();
+}
+
+// Tests that the server properly responds to shutdown command sent
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelShutdown() {
+ createHttpChannelServer();
+ std::string response;
+
+ sendHttpCommand("{ \"command\": \"shutdown\" }", response);
+ EXPECT_EQ("[ { \"result\": 0, \"text\": \"Shutting down.\" } ]", response);
+
+ EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelShutdown) {
+ testControlChannelShutdown();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelShutdown) {
+ testControlChannelShutdown();
+}
+
+// Check that the "config-set" command will replace current configuration.
+TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
+ createHttpChannelServer();
+
+ // 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 =
+ " ,\"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 << set_config_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\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"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 a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ sendHttpCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("[ { \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+ "parameter is missing for a subnet being configured "
+ "(<string>:21:17)\" } ]",
+ 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);
+
+ // Create a valid config with two subnets and no command channel.
+ // It should succeed, client should still receive the response
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Verify the HTTP control channel socket exists.
+ EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+ // Send the config-set command.
+ sendHttpCommand(os.str(), response);
+
+ // Verify the HTTP control channel socket no longer exists.
+ ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+ EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+
+ // With no command channel, should still receive the response.
+ EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
+// Check that the "config-set" command will replace current configuration.
+TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
+ 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 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\": \"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 << set_config_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);
+ EXPECT_EQ("[ { \"arguments\": { \"hash\": \"19DC7B91AB2806C11494FA4AB3D2ACF144E1F6CDDD24859F5E8F277D06B06454\" }, \"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 a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << 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
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ sendHttpCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("[ { \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+ "parameter is missing for a subnet being configured "
+ "(<string>:21:17)\" } ]",
+ 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);
+
+ // Create a valid config with two subnets and no command channel.
+ // It should succeed, client should still receive the response
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Verify the HTTP control channel socket exists.
+ EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+ // Send the config-set command.
+ sendHttpCommand(os.str(), response);
+
+ // Verify the HTTP control channel socket no longer exists.
+ ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+ EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+
+ // With no command channel, should still receive the response.
+ EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ // 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.
+void
+BaseCtrlChannelDhcpv6Test::testConfigGet() {
+ createHttpChannelServer();
+ std::string response;
+
+ sendHttpCommand("{ \"command\": \"config-get\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseListAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(cfg);
+ ASSERT_EQ(Element::map, cfg->getType());
+ EXPECT_TRUE(cfg->get("Dhcp6"));
+ EXPECT_TRUE(cfg->get("Dhcp6")->get("loggers"));
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, configGet) {
+ testConfigGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configGet) {
+ testConfigGet();
+}
// Tests if the server returns the hash of its configuration using
// config-hash-get.
-TEST_F(HttpCtrlChannelDhcpv6Test, configHashGet) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigHashGet() {
createHttpChannelServer();
std::string response;
sendHttpCommand("{ \"command\": \"config-hash-get\" }", response);
ConstElementPtr rsp;
- // The response should be a valid JSON.
- EXPECT_NO_THROW(rsp = Element::fromJSON(response));
- ASSERT_TRUE(rsp);
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr args = parseListAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+ // The parseListAnswer is trying to be smart with ignoring hash.
+ // But this time we really want to see the hash, so we'll retrieve
+ // the arguments manually.
+ args = rsp->get(0)->get(CONTROL_ARGUMENTS);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(args);
+ ASSERT_EQ(Element::map, args->getType());
+ ConstElementPtr hash = args->get("hash");
+ ASSERT_TRUE(hash);
+ ASSERT_EQ(Element::string, hash->getType());
+ // SHA-256 -> 64 hex digits.
+ EXPECT_EQ(64, hash->stringValue().size());
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, configHashGet) {
+ testConfigHashGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configHashGet) {
+ testConfigHashGet();
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
+ createHttpChannelServer();
+
+ // 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 config_test_txt = "{ \"command\": \"config-test\" \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 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 << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket
+ << logger_txt
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ std::string response;
+ sendHttpCommand(os.str(), response);
+
+ EXPECT_EQ("[ { \"arguments\": { \"hash\": \"DE129E7DD1C402721C83B74C5BBD7C330A0B705108A198CB868377031169BBC2\" }, \"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());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-test command
+ sendHttpCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("[ { \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+ "parameter is missing for a subnet being configured "
+ "(<string>:21:17)\" } ]",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Verify the HTTP control channel socket exists.
+ EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+ // Send the config-test command.
+ sendHttpCommand(os.str(), response);
+
+ // Verify the HTTP control channel socket still exists.
+ ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+ EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("[ { \"result\": 0, \"text\": \"Configuration seems sane. "
+ "Control-socket, hook-libraries, and D2 configuration were "
+ "sanity checked, but not applied.\" } ]",
+ response);
- int status;
- ConstElementPtr args = parseListAnswer(status, rsp);
- EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
- // The parseListAnswer is trying to be smart with ignoring hash.
- // But this time we really want to see the hash, so we'll retrieve
- // the arguments manually.
- args = rsp->get(0)->get(CONTROL_ARGUMENTS);
+ // Check that the config was not applied.
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
- // Ok, now roughly check if the response seems legit.
- ASSERT_TRUE(args);
- ASSERT_EQ(Element::map, args->getType());
- ConstElementPtr hash = args->get("hash");
- ASSERT_TRUE(hash);
- ASSERT_EQ(Element::string, hash->getType());
- // SHA-256 -> 64 hex digits.
- EXPECT_EQ(64, hash->stringValue().size());
+ // Clean up after the test.
+ CfgMgr::instance().clear();
}
// Verify that the "config-test" command will do what we expect.
-TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
+TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
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 set_config_txt = "{ \"command\": \"config-set\" \n";
string config_test_txt = "{ \"command\": \"config-test\" \n";
string args_txt = " \"arguments\": { \n";
" \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
string subnet_footer =
" ] \n";
- string control_socket =
+ string control_socket_header =
" ,\"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"::1\", \n"
- " \"socket-port\": 18126 \n"
+ " \"socket-port\": 18126, \n";
+ string control_socket_footer =
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
// Create a valid config with all the parts should parse
os << set_config_txt << ","
- << args_txt
- << dhcp6_cfg_txt
- << subnet1
- << subnet_footer
- << control_socket
- << logger_txt
- << "}\n" // close dhcp6
- << "}}";
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << 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);
- EXPECT_EQ("[ { \"arguments\": { \"hash\": \"DE129E7DD1C402721C83B74C5BBD7C330A0B705108A198CB868377031169BBC2\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+ EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F985ED10D250FDF13F10DF7E409EA4F467D46BC77F8FACE95AF7CB5208E6B6B2\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was indeed applied.
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << config_test_txt << ","
- << args_txt
- << dhcp6_cfg_txt
- << bad_subnet
- << subnet_footer
- << control_socket
- << "}\n" // close dhcp6
- << "}}";
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << 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
+ << "}\n" // close dhcp6
+ << "}}";
// Send the config-test command
sendHttpCommand(os.str(), response);
// Create a valid config with two subnets and no command channel.
os.str("");
os << config_test_txt << ","
- << args_txt
- << dhcp6_cfg_txt
- << subnet1
- << ",\n"
- << subnet2
- << subnet_footer
- << "}\n" // close dhcp6
- << "}}";
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
// Verify the HTTP control channel socket exists.
EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
}
// This test verifies that the DHCP server handles version-get commands
-TEST_F(HttpCtrlChannelDhcpv6Test, getVersion) {
+void
+BaseCtrlChannelDhcpv6Test::testGetVersion() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
}
-// This test verifies that the DHCP server handles status-get commands
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGet) {
+TEST_F(HttpCtrlChannelDhcpv6Test, getVersion) {
+ testGetVersion();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, getVersion) {
+ testGetVersion();
+}
+
+// This test verifies that the DHCP server handles status-get commands.
+void
+BaseCtrlChannelDhcpv6Test::testStatusGet() {
createHttpChannelServer();
// start_ is initialized by init.
EXPECT_EQ(3, found_queue_stats->size());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGet) {
+ testStatusGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGet) {
+ testStatusGet();
+}
+
// Check that status is returned even if LeaseMgr and HostMgr are not created.
-TEST_F(HttpCtrlChannelDhcpv6Test, noManagers) {
+void
+BaseCtrlChannelDhcpv6Test::testNoManagers() {
// Send the status-get command.
createHttpChannelServer();
LeaseMgrFactory::destroy();
ASSERT_EQ(Element::map, arguments->getType());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, noManagers) {
+ testNoManagers();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, noManagers) {
+ testNoManagers();
+}
+
// Checks that socket status exists in status-get responses.
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSockets) {
+void
+BaseCtrlChannelDhcpv6Test::testStatusGetSockets() {
// Create dummy interfaces to test socket status.
isc::dhcp::test::IfaceMgrTestConfig test_config(true);
ASSERT_FALSE(errors);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSockets) {
+ testStatusGetSockets();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGetSockets) {
+ testStatusGetSockets();
+}
+
// Checks that socket status includes errors in status-get responses.
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+void
+BaseCtrlChannelDhcpv6Test::testStatusGetSocketsErrors() {
// Create dummy interfaces to test socket status and add a custom down interface.
isc::dhcp::test::IfaceMgrTestConfig test_config(true);
test_config.addIface("down_interface", 4);
ASSERT_EQ("the interface down_interface is down", error->stringValue());
}
-// This test verifies that the DHCP server handles server-tag-get command
-TEST_F(HttpCtrlChannelDhcpv6Test, serverTagGet) {
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+ testStatusGetSocketsErrors();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+ testStatusGetSocketsErrors();
+}
+
+// This test verifies that the DHCP server handles server-tag-get command.
+void
+BaseCtrlChannelDhcpv6Test::testServerTagGet() {
createHttpChannelServer();
std::string response;
EXPECT_EQ(expected, response);
}
-// This test verifies that the DHCP server handles config-backend-pull command
-TEST_F(HttpCtrlChannelDhcpv6Test, configBackendPull) {
+TEST_F(HttpCtrlChannelDhcpv6Test, serverTagGet) {
+ testServerTagGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, serverTagGet) {
+ testServerTagGet();
+}
+
+// This test verifies that the DHCP server handles config-backend-pull command.
+void
+BaseCtrlChannelDhcpv6Test::testConfigBackendPull() {
createHttpChannelServer();
std::string response;
EXPECT_EQ(expected, response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configBackendPull) {
+ testConfigBackendPull();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configBackendPull) {
+ testConfigBackendPull();
+}
+
// This test verifies that the DHCP server immediately reclaims expired
-// leases on leases-reclaim command
-TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+// leases on leases-reclaim command.
+void
+BaseCtrlChannelDhcpv6Test::testControlLeasesReclaim() {
createHttpChannelServer();
// Create expired leases. Leases are expired by 40 seconds ago
EXPECT_TRUE(lease1->stateExpiredReclaimed());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+ testControlLeasesReclaim();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+ testControlLeasesReclaim();
+}
+
// This test verifies that the DHCP server immediately reclaims expired
-// leases on leases-reclaim command with remove = true
-TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+// leases on leases-reclaim command with remove = true.
+void
+BaseCtrlChannelDhcpv6Test::testControlLeasesReclaimRemove() {
createHttpChannelServer();
// Create expired leases. Leases are expired by 40 seconds ago
ASSERT_FALSE(lease1);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+ testControlLeasesReclaimRemove();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+ testControlLeasesReclaimRemove();
+}
+
// Tests that the server properly responds to statistics commands. Note this
// is really only intended to verify that the appropriate Statistics handler
// is called based on the command. It is not intended to be an exhaustive
// test of Dhcpv6 statistics.
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelStats) {
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelStats() {
createHttpChannelServer();
std::string response;
response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelStats) {
+ testControlChannelStats();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelStats) {
+ testControlChannelStats();
+}
+
// Tests that the server properly responds to list-commands command sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, listCommands) {
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testListCommands() {
createHttpChannelServer();
std::string response;
checkListCommands(rsp, "statistic-sample-count-set-all");
}
+TEST_F(HttpCtrlChannelDhcpv6Test, listCommands) {
+ testListCommands();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, listCommands) {
+ testListCommands();
+}
+
// Tests if config-write can be called without any parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, configWriteNoFilename) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigWriteNoFilename() {
createHttpChannelServer();
std::string response;
::remove("test1.json");
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configWriteNoFilename) {
+ testConfigWriteNoFilename();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configWriteNoFilename) {
+ testConfigWriteNoFilename();
+}
+
// Tests if config-write can be called with a valid filename as parameter.
-TEST_F(HttpCtrlChannelDhcpv6Test, configWriteFilename) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigWriteFilename() {
createHttpChannelServer();
std::string response;
::remove("test2.json");
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configWriteFilename) {
+ testConfigWriteFilename();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configWriteFilename) {
+ testConfigWriteFilename();
+}
+
// Tests if config-reload attempts to reload a file and reports that the
// file is missing.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadMissingFile) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadMissingFile() {
createHttpChannelServer();
std::string response;
response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadMissingFile) {
+ testConfigReloadMissingFile();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadMissingFile) {
+ testConfigReloadMissingFile();
+}
+
// Tests if config-reload attempts to reload a file and reports that the
// file is not a valid JSON.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadBrokenFile() {
createHttpChannelServer();
std::string response;
::remove("test7.json");
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+ testConfigReloadBrokenFile();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+ testConfigReloadBrokenFile();
+}
+
// Tests if config-reload attempts to reload a file and reports that the
// file is loaded correctly.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadValid) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadValid() {
createHttpChannelServer();
std::string response;
::remove("test8.json");
}
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadValid) {
+ testConfigReloadValid();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadValid) {
+ testConfigReloadValid();
+}
+
// Tests if config-reload attempts to reload a file and reports that the
// file is loaded correctly.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadDetectInterfaces() {
interfaces_ = "\"eth0\"";
IfacePtr eth0 = IfaceMgrTestConfig::createIface("eth0", ETH0_INDEX,
"11:22:33:44:55:66");
::remove("test8.json");
}
-// This test verifies that disable DHCP service command performs sanity check on
-// parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+ testConfigReloadDetectInterfaces();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+ testConfigReloadDetectInterfaces();
+}
+
+// This test verifies that disable DHCP service command performs
+// sanity check on parameters.
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableBadParam() {
createHttpChannelServer();
std::string response;
response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+ testDhcpDisableBadParam();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+ testDhcpDisableBadParam();
+}
+
// This test verifies if it is possible to disable DHCP service via command.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisable) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisable() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(server_->network_state_->isServiceEnabled());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisable) {
+ testDhcpDisable();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisable) {
+ testDhcpDisable();
+}
+
// This test verifies if it is possible to disable DHCP service using
// the origin-id.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableOriginId() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(server_->network_state_->isServiceEnabled());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+ testDhcpDisableOriginId();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+ testDhcpDisableOriginId();
+}
+
// This test verifies that it is possible to disable DHCP service for a short
// period of time, after which the service is automatically enabled.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableTemporarily() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(server_->network_state_->isDelayedEnableService());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+ testDhcpDisableTemporarily();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+ testDhcpDisableTemporarily();
+}
+
// This test verifies that enable DHCP service command performs sanity check on
// parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnableBadParam() {
createHttpChannelServer();
std::string response;
ConstElementPtr rsp;
response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+ testDhcpEnableBadParam();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+ testDhcpEnableBadParam();
+}
+
// This test verifies if it is possible to enable DHCP service via command.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnable) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnable() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(server_->network_state_->isServiceEnabled());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnable) {
+ testDhcpEnable();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnable) {
+ testDhcpEnable();
+}
+
// This test verifies if it is possible to enable DHCP service using
// the origin-id.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnableOriginId() {
createHttpChannelServer();
std::string response;
EXPECT_TRUE(server_->network_state_->isServiceEnabled());
}
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+ testDhcpEnableOriginId();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+ testDhcpEnableOriginId();
+}
+
/// Verify that concurrent connections over the HTTP control channel can be
/// established.
TEST_F(HttpCtrlChannelDhcpv6Test, concurrentConnections) {
}
}
+/// Verify that concurrent connections over the HTTPS control channel can be
+/// established.
+TEST_F(HttpsCtrlChannelDhcpv6Test, concurrentConnections) {
+ EXPECT_NO_THROW(createHttpChannelServer());
+
+ const size_t NB = 5;
+ vector<IOServicePtr> io_services;
+ vector<TestHttpsClientPtr> clients;
+ vector<TlsContextPtr> tls_contexts;
+
+ // Create clients.
+ for (size_t i = 0; i < NB; ++i) {
+ IOServicePtr io_service(new IOService());
+ io_services.push_back(io_service);
+ TlsContextPtr tls_context;
+ configClient(tls_context);
+ tls_contexts.push_back(tls_context);
+ TestHttpsClientPtr client(new TestHttpsClient(io_service,
+ tls_context,
+ SERVER_ADDRESS,
+ SERVER_PORT));
+ clients.push_back(client);
+ }
+ ASSERT_EQ(NB, io_services.size());
+ ASSERT_EQ(NB, clients.size());
+
+ // Send requests and receive responses.
+ atomic<size_t> terminated;
+ terminated = 0;
+ vector<thread> threads;
+ const string command = "{ \"command\": \"list-commands\" }";
+ for (size_t i = 0; i < NB; ++i) {
+ threads.push_back(thread([&, i] () {
+ TestHttpsClientPtr client = clients[i];
+ ASSERT_TRUE(client);
+ client->startRequest(buildPostStr(command));
+ IOServicePtr io_service = io_services[i];
+ ASSERT_TRUE(io_service);
+ io_service->run();
+ ASSERT_TRUE(client->receiveDone());
+ HttpResponsePtr hr;
+ ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+ string response = hr->getBody();
+ EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+ client->close();
+ ++terminated;
+ }));
+ }
+ ASSERT_EQ(NB, threads.size());
+
+ // Run the service IO services with a timeout.
+ IntervalTimer test_timer(getIOService());
+ bool timeout = false;
+ test_timer.setup([&timeout] () { timeout = true; },
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ while (!timeout && (terminated < NB)) {
+ getIOService()->poll();
+ }
+ test_timer.cancel();
+ EXPECT_FALSE(timeout);
+
+ // Cleanup clients.
+ for (IOServicePtr io_service : io_services) {
+ io_service->stopAndPoll();
+ }
+ for (auto th = threads.begin(); th != threads.end(); ++th) {
+ th->join();
+ }
+}
+
// This test verifies that the server can receive and process a large command.
-TEST_F(HttpCtrlChannelDhcpv6Test, longCommand) {
+void
+BaseCtrlChannelDhcpv6Test::testLongCommand() {
ostringstream command;
response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, longCommand) {
+ testLongCommand();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, longCommand) {
+ testLongCommand();
+}
+
// This test verifies that the server can send long response to the client.
-TEST_F(HttpCtrlChannelDhcpv6Test, longResponse) {
+void
+BaseCtrlChannelDhcpv6Test::testLongResponse() {
// We need to generate large response. The simplest way is to create
// a command and a handler which will generate some static response
// of a desired size.
EXPECT_EQ(reference_response, response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, longResponse) {
+ testLongResponse();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, longResponse) {
+ testLongResponse();
+}
+
// This test verifies that the server signals timeout if the transmission
// takes too long, having received no data from the client.
-TEST_F(HttpCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+void
+BaseCtrlChannelDhcpv6Test::testConnectionTimeoutNoData() {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
}
+TEST_F(HttpCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+ testConnectionTimeoutNoData();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+ testConnectionTimeoutNoData();
+}
+
} // End of anonymous namespace