mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 05:27:55 +00:00
[master] Merge branch 'trac3902' (extended unit-tests for command channel)
This commit is contained in:
commit
a50d64cc41
@ -111,6 +111,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtes
|
|||||||
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
|
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
|
||||||
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
|
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
|
||||||
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
|
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
|
||||||
|
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
|
||||||
endif
|
endif
|
||||||
|
|
||||||
noinst_EXTRA_DIST = configs-list.txt
|
noinst_EXTRA_DIST = configs-list.txt
|
||||||
|
@ -18,7 +18,9 @@
|
|||||||
#include <config/command_mgr.h>
|
#include <config/command_mgr.h>
|
||||||
#include <dhcp/dhcp4.h>
|
#include <dhcp/dhcp4.h>
|
||||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||||
|
#include <dhcpsrv/cfgmgr.h>
|
||||||
#include <hooks/hooks_manager.h>
|
#include <hooks/hooks_manager.h>
|
||||||
|
#include <testutils/unix_control_client.h>
|
||||||
|
|
||||||
#include "marker_file.h"
|
#include "marker_file.h"
|
||||||
#include "test_libraries.h"
|
#include "test_libraries.h"
|
||||||
@ -47,19 +49,99 @@ namespace {
|
|||||||
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
|
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
|
||||||
// "Naked" DHCPv4 server, exposes internal fields
|
// "Naked" DHCPv4 server, exposes internal fields
|
||||||
public:
|
public:
|
||||||
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
|
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
|
||||||
|
|
||||||
|
/// Expose internal methods for the sake of testing
|
||||||
|
using Dhcpv4Srv::receivePacket;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CtrlDhcpv4SrvTest : public ::testing::Test {
|
/// @brief Fixture class intended for testin control channel in the DHCPv4Srv
|
||||||
|
class CtrlChannelDhcpv4SrvTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
CtrlDhcpv4SrvTest() {
|
|
||||||
|
/// @brief Path to the UNIX socket being used to communicate with the server
|
||||||
|
std::string socket_path_;
|
||||||
|
|
||||||
|
/// @brief Pointer to the tested server object
|
||||||
|
boost::shared_ptr<NakedControlledDhcpv4Srv> server_;
|
||||||
|
|
||||||
|
/// @brief Default constructor
|
||||||
|
///
|
||||||
|
/// Sets socket path to its default value.
|
||||||
|
CtrlChannelDhcpv4SrvTest() {
|
||||||
|
const char* env = getenv("KEA_SOCKET_TEST_DIR");
|
||||||
|
if (env) {
|
||||||
|
socket_path_ = string(env) + "/kea4.sock";
|
||||||
|
} else {
|
||||||
|
socket_path_ = string(TEST_DATA_BUILDDIR) + "/kea4.sock";
|
||||||
|
}
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
|
// This is a workaround for odd problems with gtest. gtest does
|
||||||
|
// shady with socket decriptors. In particular, sometimes we
|
||||||
|
// get 0 as descriptor for socket() call. Technically it is valid,
|
||||||
|
// but then gtest closes descriptor 0 and the socket becomes
|
||||||
|
// unusable. This workaround opens up one file decriptor. In case
|
||||||
|
// 0 is available, it will be consumed here.
|
||||||
|
dummy_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (dummy_fd_ == 0) {
|
||||||
|
std::cout << "Socket descriptor 0 workaround is useful." << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~CtrlDhcpv4SrvTest() {
|
/// @brief Destructor
|
||||||
|
~CtrlChannelDhcpv4SrvTest() {
|
||||||
|
server_.reset();
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
|
// close dummy descriptor
|
||||||
|
close(dummy_fd_);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void createUnixChannelServer() {
|
||||||
|
::remove(socket_path_.c_str());
|
||||||
|
|
||||||
|
// Just a simple config. The important part here is the socket
|
||||||
|
// location information.
|
||||||
|
std::string header =
|
||||||
|
"{"
|
||||||
|
" \"interfaces-config\": {"
|
||||||
|
" \"interfaces\": [ \"*\" ]"
|
||||||
|
" },"
|
||||||
|
" \"rebind-timer\": 2000, "
|
||||||
|
" \"renew-timer\": 1000, "
|
||||||
|
" \"subnet4\": [ ],"
|
||||||
|
" \"valid-lifetime\": 4000,"
|
||||||
|
" \"control-socket\": {"
|
||||||
|
" \"socket-type\": \"unix\","
|
||||||
|
" \"socket-name\": \"";
|
||||||
|
|
||||||
|
std::string footer =
|
||||||
|
"\" },"
|
||||||
|
" \"lease-database\": {"
|
||||||
|
" \"type\": \"memfile\", \"persist\": false }"
|
||||||
|
"}";
|
||||||
|
|
||||||
|
// Fill in the socket-name value with socket_path_ to
|
||||||
|
// make the actual configuration text.
|
||||||
|
std::string config_txt = header + socket_path_ + footer;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
|
||||||
|
|
||||||
|
ConstElementPtr config = Element::fromJSON(config_txt);
|
||||||
|
ConstElementPtr answer = server_->processConfig(config);
|
||||||
|
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_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @brief Reset hooks data
|
/// @brief Reset hooks data
|
||||||
///
|
///
|
||||||
/// Resets the data for the hooks-related portion of the test by ensuring
|
/// Resets the data for the hooks-related portion of the test by ensuring
|
||||||
@ -71,79 +153,61 @@ public:
|
|||||||
// Get rid of any marker files.
|
// Get rid of any marker files.
|
||||||
static_cast<void>(remove(LOAD_MARKER_FILE));
|
static_cast<void>(remove(LOAD_MARKER_FILE));
|
||||||
static_cast<void>(remove(UNLOAD_MARKER_FILE));
|
static_cast<void>(remove(UNLOAD_MARKER_FILE));
|
||||||
|
|
||||||
|
IfaceMgr::instance().deleteAllExternalSockets();
|
||||||
|
CfgMgr::instance().clear();
|
||||||
|
|
||||||
|
// Remove unix socket file
|
||||||
|
::remove(socket_path_.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief sends commands over specified UNIX socket
|
/// @brief Conducts a command/response exchange via UnixCommandSocket
|
||||||
///
|
///
|
||||||
/// @param command command to be sent (should be valid JSON)
|
/// This method connects to the given server over the given socket path.
|
||||||
/// @param response response received (expected to be a valid JSON)
|
/// If successful, it then sends the given command and retrieves the
|
||||||
/// @param socket_path UNIX socket path
|
/// server's response. Note that it calls the server's receivePacket()
|
||||||
|
/// method where needed to cause the server to process IO events on
|
||||||
|
/// control channel the control channel sockets.
|
||||||
///
|
///
|
||||||
/// @return true if send/response exchange was successful, false otherwise
|
/// @param command the command text to execute in JSON form
|
||||||
bool sendCommandUnixSocket(const std::string& command,
|
/// @param response variable into which the received response should be
|
||||||
std::string& response,
|
/// placed.
|
||||||
const std::string& socket_path) {
|
void sendUnixCommand(const std::string& command, std::string& response) {
|
||||||
|
response = "";
|
||||||
|
boost::scoped_ptr<UnixControlClient> client;
|
||||||
|
client.reset(new UnixControlClient());
|
||||||
|
ASSERT_TRUE(client);
|
||||||
|
|
||||||
// Create UNIX socket
|
// Connect and then call server's receivePacket() so it can
|
||||||
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
// detect the control socket connect and call the accept handler
|
||||||
if (socket_fd < 0) {
|
ASSERT_TRUE(client->connectToServer(socket_path_));
|
||||||
ADD_FAILURE() << "Failed to open unix stream socket.";
|
ASSERT_NO_THROW(server_->receivePacket(0));
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare socket address
|
// Send the command and then call server's receivePacket() so it can
|
||||||
struct sockaddr_un srv_addr;
|
// detect the inbound data and call the read handler
|
||||||
memset(&srv_addr, 0, sizeof(struct sockaddr_un));
|
ASSERT_TRUE(client->sendCommand(command));
|
||||||
srv_addr.sun_family = AF_UNIX;
|
ASSERT_NO_THROW(server_->receivePacket(0));
|
||||||
strncpy(srv_addr.sun_path, socket_path.c_str(), sizeof(srv_addr.sun_path));
|
|
||||||
socklen_t len = sizeof(srv_addr);
|
|
||||||
|
|
||||||
// Connect to the specified UNIX socket
|
// Read the response generated by the server. Note that getResponse
|
||||||
int status = connect(socket_fd, (struct sockaddr*)&srv_addr, len);
|
// only fails if there an IO error or no response data was present.
|
||||||
if (status == -1) {
|
// It is not based on the response content.
|
||||||
ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd
|
ASSERT_TRUE(client->getResponse(response));
|
||||||
<< ", path=" << socket_path;
|
|
||||||
close(socket_fd);
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Now disconnect and process the close event
|
||||||
// Send command
|
client->disconnectFromServer();
|
||||||
cout << "Sending command: " << command << endl;
|
ASSERT_NO_THROW(server_->receivePacket(0));
|
||||||
int bytes_sent = send(socket_fd, command.c_str(), command.length(), 0);
|
|
||||||
if (bytes_sent < command.length()) {
|
|
||||||
ADD_FAILURE() << "Failed to send " << command.length()
|
|
||||||
<< " bytes, send() returned " << bytes_sent;
|
|
||||||
close(socket_fd);
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive response
|
|
||||||
/// @todo: this may block if server fails to respond. Some sort of
|
|
||||||
/// of a timer is needed.
|
|
||||||
char buf[65536];
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
int bytes_rcvd = recv(socket_fd, buf, sizeof(buf), 0);
|
|
||||||
if (bytes_rcvd < 0) {
|
|
||||||
ADD_FAILURE() << "Failed to receive a response. recv() returned "
|
|
||||||
<< bytes_rcvd;
|
|
||||||
close(socket_fd);
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the response to a string, close the socket and return
|
|
||||||
response = string(buf, bytes_rcvd);
|
|
||||||
cout << "Received response: " << response << endl;
|
|
||||||
close(socket_fd);
|
|
||||||
return (true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief dummy file descriptor
|
||||||
|
///
|
||||||
|
/// See ctor for details.
|
||||||
|
int dummy_fd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(CtrlDhcpv4SrvTest, commands) {
|
TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
|
||||||
|
|
||||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
|
||||||
ASSERT_NO_THROW(
|
ASSERT_NO_THROW(
|
||||||
srv.reset(new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000))
|
server_.reset(new NakedControlledDhcpv4Srv());
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use empty parameters list
|
// Use empty parameters list
|
||||||
@ -172,13 +236,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
|
|||||||
|
|
||||||
// Check that the "libreload" command will reload libraries
|
// Check that the "libreload" command will reload libraries
|
||||||
|
|
||||||
TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
TEST_F(CtrlChannelDhcpv4SrvTest, libreload) {
|
||||||
|
|
||||||
// Sending commands for processing now requires a server that can process
|
// Sending commands for processing now requires a server that can process
|
||||||
// them.
|
// them.
|
||||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
|
||||||
ASSERT_NO_THROW(
|
ASSERT_NO_THROW(
|
||||||
srv.reset(new ControlledDhcpv4Srv(0))
|
server_.reset(new NakedControlledDhcpv4Srv());
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure no marker files to start with.
|
// Ensure no marker files to start with.
|
||||||
@ -223,7 +286,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This test checks which commands are registered by the DHCPv4 server.
|
// This test checks which commands are registered by the DHCPv4 server.
|
||||||
TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
|
||||||
|
|
||||||
ConstElementPtr list_cmds = createCommand("list-commands");
|
ConstElementPtr list_cmds = createCommand("list-commands");
|
||||||
ConstElementPtr answer;
|
ConstElementPtr answer;
|
||||||
@ -236,22 +299,25 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|||||||
EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
|
EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
|
||||||
|
|
||||||
// Created server should register several additional commands.
|
// Created server should register several additional commands.
|
||||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
|
||||||
ASSERT_NO_THROW(
|
ASSERT_NO_THROW(
|
||||||
srv.reset(new ControlledDhcpv4Srv(0));
|
server_.reset(new NakedControlledDhcpv4Srv());
|
||||||
);
|
);
|
||||||
|
|
||||||
EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
|
EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
|
||||||
ASSERT_TRUE(answer);
|
ASSERT_TRUE(answer);
|
||||||
ASSERT_TRUE(answer->get("arguments"));
|
ASSERT_TRUE(answer->get("arguments"));
|
||||||
EXPECT_EQ("[ \"list-commands\", \"shutdown\", "
|
std::string command_list = answer->get("arguments")->str();
|
||||||
"\"statistic-get\", \"statistic-get-all\", "
|
|
||||||
"\"statistic-remove\", \"statistic-remove-all\", "
|
EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
|
||||||
"\"statistic-reset\", \"statistic-reset-all\" ]",
|
EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
|
||||||
answer->get("arguments")->str());
|
EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
|
||||||
|
EXPECT_TRUE(command_list.find("\"statistic-remove\"") != string::npos);
|
||||||
|
EXPECT_TRUE(command_list.find("\"statistic-remove-all\"") != string::npos);
|
||||||
|
EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
|
||||||
|
EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
|
||||||
|
|
||||||
// Ok, and now delete the server. It should deregister its commands.
|
// Ok, and now delete the server. It should deregister its commands.
|
||||||
srv.reset();
|
server_.reset();
|
||||||
|
|
||||||
// The list should be (almost) empty again.
|
// The list should be (almost) empty again.
|
||||||
EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
|
EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
|
||||||
@ -260,4 +326,77 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|||||||
EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
|
EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that the server properly responds to invalid commands sent
|
||||||
|
// via ControlChannel
|
||||||
|
TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelNegative) {
|
||||||
|
createUnixChannelServer();
|
||||||
|
std::string response;
|
||||||
|
|
||||||
|
sendUnixCommand("{ \"command\": \"bogus\" }", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 1,"
|
||||||
|
" \"text\": \"'bogus' command not supported.\" }", response);
|
||||||
|
|
||||||
|
sendUnixCommand("utter nonsense", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 1, "
|
||||||
|
"\"text\": \"error: unexpected character u in <string>:1:2\" }",
|
||||||
|
response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the server properly responds to shtudown command sent
|
||||||
|
// via ControlChannel
|
||||||
|
TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
|
||||||
|
createUnixChannelServer();
|
||||||
|
std::string response;
|
||||||
|
|
||||||
|
sendUnixCommand("{ \"command\": \"shutdown\" }", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Dhcpv4 statistics.
|
||||||
|
TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
|
||||||
|
createUnixChannelServer();
|
||||||
|
std::string response;
|
||||||
|
|
||||||
|
// Check statistic-get
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-get\", "
|
||||||
|
" \"arguments\": {"
|
||||||
|
" \"name\":\"bogus\" }}", response);
|
||||||
|
EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
|
||||||
|
|
||||||
|
// Check statistic-get-all
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
|
||||||
|
" \"arguments\": {}}", response);
|
||||||
|
EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
|
||||||
|
|
||||||
|
// Check statistic-reset
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-reset\", "
|
||||||
|
" \"arguments\": {"
|
||||||
|
" \"name\":\"bogus\" }}", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
|
||||||
|
response);
|
||||||
|
|
||||||
|
// Check statistic-reset-all
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
|
||||||
|
" \"arguments\": {}}", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 0, \"text\": "
|
||||||
|
"\"All statistics reset to neutral values.\" }", response);
|
||||||
|
|
||||||
|
// Check statistic-remove
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-remove\", "
|
||||||
|
" \"arguments\": {"
|
||||||
|
" \"name\":\"bogus\" }}", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
|
||||||
|
response);
|
||||||
|
|
||||||
|
// Check statistic-remove-all
|
||||||
|
sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
|
||||||
|
" \"arguments\": {}}", response);
|
||||||
|
EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
|
||||||
|
response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // End of anonymous namespace
|
} // End of anonymous namespace
|
||||||
|
@ -113,6 +113,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
|
|||||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
|
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
|
||||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
|
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
|
||||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
|
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
|
||||||
|
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <dhcpsrv/cfgmgr.h>
|
#include <dhcpsrv/cfgmgr.h>
|
||||||
#include <dhcp6/ctrl_dhcp6_srv.h>
|
#include <dhcp6/ctrl_dhcp6_srv.h>
|
||||||
#include <hooks/hooks_manager.h>
|
#include <hooks/hooks_manager.h>
|
||||||
|
#include <testutils/unix_control_client.h>
|
||||||
|
|
||||||
#include "marker_file.h"
|
#include "marker_file.h"
|
||||||
#include "test_libraries.h"
|
#include "test_libraries.h"
|
||||||
@ -39,150 +40,6 @@ using namespace isc::hooks;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// Class that acts as a UnixCommandSocket client
|
|
||||||
/// It can connect to an open UnixCommandSocket and exchange ControlChannel
|
|
||||||
/// commands and responses.
|
|
||||||
class UnixControlClient {
|
|
||||||
public:
|
|
||||||
UnixControlClient() {
|
|
||||||
socket_fd_ = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
~UnixControlClient() {
|
|
||||||
disconnectFromServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes the Control Channel socket
|
|
||||||
void disconnectFromServer() {
|
|
||||||
if (socket_fd_ >= 0) {
|
|
||||||
static_cast<void>(close(socket_fd_));
|
|
||||||
socket_fd_ = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Connects to a Unix socket at the given path
|
|
||||||
/// @param socket_path pathname of the socket to open
|
|
||||||
/// @return true if the connect was successful, false otherwise
|
|
||||||
bool connectToServer(const std::string& socket_path) {
|
|
||||||
// Create UNIX socket
|
|
||||||
socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
if (socket_fd_ < 0) {
|
|
||||||
const char* errmsg = strerror(errno);
|
|
||||||
ADD_FAILURE() << "Failed to open unix stream socket: " << errmsg;
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_un srv_addr;
|
|
||||||
if (socket_path.size() > sizeof(srv_addr.sun_path) - 1) {
|
|
||||||
ADD_FAILURE() << "Socket path specified (" << socket_path
|
|
||||||
<< ") is larger than " << (sizeof(srv_addr.sun_path) - 1)
|
|
||||||
<< " allowed.";
|
|
||||||
disconnectFromServer();
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare socket address
|
|
||||||
memset(&srv_addr, 0, sizeof(srv_addr));
|
|
||||||
srv_addr.sun_family = AF_UNIX;
|
|
||||||
strncpy(srv_addr.sun_path, socket_path.c_str(),
|
|
||||||
sizeof(srv_addr.sun_path));
|
|
||||||
socklen_t len = sizeof(srv_addr);
|
|
||||||
|
|
||||||
// Connect to the specified UNIX socket
|
|
||||||
int status = connect(socket_fd_, (struct sockaddr*)&srv_addr, len);
|
|
||||||
if (status == -1) {
|
|
||||||
const char* errmsg = strerror(errno);
|
|
||||||
ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd_
|
|
||||||
<< ", path=" << socket_path << " : " << errmsg;
|
|
||||||
disconnectFromServer();
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Sends the given command across the open Control Channel
|
|
||||||
/// @param command the command text to execute in JSON form
|
|
||||||
/// @return true if the send succeeds, false otherwise
|
|
||||||
bool sendCommand(const std::string& command) {
|
|
||||||
// Send command
|
|
||||||
int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
|
|
||||||
if (bytes_sent < command.length()) {
|
|
||||||
const char* errmsg = strerror(errno);
|
|
||||||
ADD_FAILURE() << "Failed to send " << command.length()
|
|
||||||
<< " bytes, send() returned " << bytes_sent
|
|
||||||
<< " : " << errmsg;
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Reads the response text from the open Control Channel
|
|
||||||
/// @param response variable into which the received response should be
|
|
||||||
/// placed.
|
|
||||||
/// @return true if data was successfully read from the socket,
|
|
||||||
/// false otherwise
|
|
||||||
bool getResponse(std::string& response) {
|
|
||||||
// Receive response
|
|
||||||
// @todo implement select check to see if data is waiting
|
|
||||||
char buf[65536];
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
switch (selectCheck()) {
|
|
||||||
case -1: {
|
|
||||||
const char* errmsg = strerror(errno);
|
|
||||||
ADD_FAILURE() << "getResponse - select failed: " << errmsg;
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
case 0:
|
|
||||||
ADD_FAILURE() << "No response data sent";
|
|
||||||
return (false);
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytes_rcvd = recv(socket_fd_, buf, sizeof(buf), 0);
|
|
||||||
if (bytes_rcvd < 0) {
|
|
||||||
const char* errmsg = strerror(errno);
|
|
||||||
ADD_FAILURE() << "Failed to receive a response. recv() returned "
|
|
||||||
<< bytes_rcvd << " : " << errmsg;
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes_rcvd >= sizeof(buf)) {
|
|
||||||
ADD_FAILURE() << "Response size too large: " << bytes_rcvd;
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the response to a string
|
|
||||||
response = string(buf, bytes_rcvd);
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @brief Uses select to poll the Control Channel for data waiting
|
|
||||||
/// @return -1 on error, 0 if no data is available, 1 if data is ready
|
|
||||||
int selectCheck() {
|
|
||||||
int maxfd = 0;
|
|
||||||
|
|
||||||
fd_set read_fds;
|
|
||||||
FD_ZERO(&read_fds);
|
|
||||||
|
|
||||||
// Add this socket to listening set
|
|
||||||
FD_SET(socket_fd_, &read_fds);
|
|
||||||
maxfd = socket_fd_;
|
|
||||||
|
|
||||||
struct timeval select_timeout;
|
|
||||||
select_timeout.tv_sec = 0;
|
|
||||||
select_timeout.tv_usec = 0;
|
|
||||||
|
|
||||||
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Retains the fd of the open socket
|
|
||||||
int socket_fd_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
|
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
|
||||||
@ -191,11 +48,8 @@ public:
|
|||||||
NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
|
NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Exposes server's receivePacket method
|
/// Expose internal methods for the sake of testing
|
||||||
virtual Pkt6Ptr receivePacket(int timeout) {
|
using Dhcpv6Srv::receivePacket;
|
||||||
return(Dhcpv6Srv::receivePacket(timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CtrlDhcpv6SrvTest : public ::testing::Test {
|
class CtrlDhcpv6SrvTest : public ::testing::Test {
|
||||||
@ -228,9 +82,16 @@ public:
|
|||||||
|
|
||||||
class CtrlChannelDhcpv6SrvTest : public CtrlDhcpv6SrvTest {
|
class CtrlChannelDhcpv6SrvTest : public CtrlDhcpv6SrvTest {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/// @brief Path to the UNIX socket being used to communicate with the server
|
||||||
std::string socket_path_;
|
std::string socket_path_;
|
||||||
|
|
||||||
|
/// @brief Pointer to the tested server object
|
||||||
boost::shared_ptr<NakedControlledDhcpv6Srv> server_;
|
boost::shared_ptr<NakedControlledDhcpv6Srv> server_;
|
||||||
|
|
||||||
|
/// @brief Default constructor
|
||||||
|
///
|
||||||
|
/// Sets socket path to its default value.
|
||||||
CtrlChannelDhcpv6SrvTest() {
|
CtrlChannelDhcpv6SrvTest() {
|
||||||
const char* env = getenv("KEA_SOCKET_TEST_DIR");
|
const char* env = getenv("KEA_SOCKET_TEST_DIR");
|
||||||
if (env) {
|
if (env) {
|
||||||
@ -241,6 +102,7 @@ public:
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Destructor
|
||||||
~CtrlChannelDhcpv6SrvTest() {
|
~CtrlChannelDhcpv6SrvTest() {
|
||||||
server_.reset();
|
server_.reset();
|
||||||
reset();
|
reset();
|
||||||
|
@ -11,6 +11,7 @@ noinst_LTLIBRARIES = libkea-testutils.la
|
|||||||
|
|
||||||
libkea_testutils_la_SOURCES = srv_test.h srv_test.cc
|
libkea_testutils_la_SOURCES = srv_test.h srv_test.cc
|
||||||
libkea_testutils_la_SOURCES += dnsmessage_test.h dnsmessage_test.cc
|
libkea_testutils_la_SOURCES += dnsmessage_test.h dnsmessage_test.cc
|
||||||
|
libkea_testutils_la_SOURCES += unix_control_client.h unix_control_client.cc
|
||||||
libkea_testutils_la_SOURCES += mockups.h
|
libkea_testutils_la_SOURCES += mockups.h
|
||||||
libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||||
libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
|
libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
|
||||||
|
150
src/lib/testutils/unix_control_client.cc
Normal file
150
src/lib/testutils/unix_control_client.cc
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <testutils/unix_control_client.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dhcp {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
UnixControlClient::UnixControlClient() {
|
||||||
|
socket_fd_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnixControlClient::~UnixControlClient() {
|
||||||
|
disconnectFromServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Closes the Control Channel socket
|
||||||
|
void UnixControlClient::disconnectFromServer() {
|
||||||
|
if (socket_fd_ >= 0) {
|
||||||
|
static_cast<void>(close(socket_fd_));
|
||||||
|
socket_fd_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnixControlClient::connectToServer(const std::string& socket_path) {
|
||||||
|
// Create UNIX socket
|
||||||
|
socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (socket_fd_ < 0) {
|
||||||
|
const char* errmsg = strerror(errno);
|
||||||
|
ADD_FAILURE() << "Failed to open unix stream socket: " << errmsg;
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un srv_addr;
|
||||||
|
if (socket_path.size() > sizeof(srv_addr.sun_path) - 1) {
|
||||||
|
ADD_FAILURE() << "Socket path specified (" << socket_path
|
||||||
|
<< ") is larger than " << (sizeof(srv_addr.sun_path) - 1)
|
||||||
|
<< " allowed.";
|
||||||
|
disconnectFromServer();
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare socket address
|
||||||
|
memset(&srv_addr, 0, sizeof(srv_addr));
|
||||||
|
srv_addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(srv_addr.sun_path, socket_path.c_str(),
|
||||||
|
sizeof(srv_addr.sun_path));
|
||||||
|
socklen_t len = sizeof(srv_addr);
|
||||||
|
|
||||||
|
// Connect to the specified UNIX socket
|
||||||
|
int status = connect(socket_fd_, (struct sockaddr*)&srv_addr, len);
|
||||||
|
if (status == -1) {
|
||||||
|
const char* errmsg = strerror(errno);
|
||||||
|
ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd_
|
||||||
|
<< ", path=" << socket_path << " : " << errmsg;
|
||||||
|
disconnectFromServer();
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnixControlClient::sendCommand(const std::string& command) {
|
||||||
|
// Send command
|
||||||
|
int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
|
||||||
|
if (bytes_sent < command.length()) {
|
||||||
|
const char* errmsg = strerror(errno);
|
||||||
|
ADD_FAILURE() << "Failed to send " << command.length()
|
||||||
|
<< " bytes, send() returned " << bytes_sent
|
||||||
|
<< " : " << errmsg;
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnixControlClient::getResponse(std::string& response) {
|
||||||
|
// Receive response
|
||||||
|
char buf[65536];
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
switch (selectCheck()) {
|
||||||
|
case -1: {
|
||||||
|
const char* errmsg = strerror(errno);
|
||||||
|
ADD_FAILURE() << "getResponse - select failed: " << errmsg;
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
ADD_FAILURE() << "No response data sent";
|
||||||
|
return (false);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytes_rcvd = recv(socket_fd_, buf, sizeof(buf), 0);
|
||||||
|
if (bytes_rcvd < 0) {
|
||||||
|
const char* errmsg = strerror(errno);
|
||||||
|
ADD_FAILURE() << "Failed to receive a response. recv() returned "
|
||||||
|
<< bytes_rcvd << " : " << errmsg;
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_rcvd >= sizeof(buf)) {
|
||||||
|
ADD_FAILURE() << "Response size too large: " << bytes_rcvd;
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the response to a string
|
||||||
|
response = std::string(buf, bytes_rcvd);
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UnixControlClient::selectCheck() {
|
||||||
|
int maxfd = 0;
|
||||||
|
|
||||||
|
fd_set read_fds;
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
|
||||||
|
// Add this socket to listening set
|
||||||
|
FD_SET(socket_fd_, &read_fds);
|
||||||
|
maxfd = socket_fd_;
|
||||||
|
|
||||||
|
struct timeval select_timeout;
|
||||||
|
select_timeout.tv_sec = 0;
|
||||||
|
select_timeout.tv_usec = 0;
|
||||||
|
|
||||||
|
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
71
src/lib/testutils/unix_control_client.h
Normal file
71
src/lib/testutils/unix_control_client.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef UNIX_CONTROL_CLIENT_H
|
||||||
|
#define UNIX_CONTROL_CLIENT_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dhcp {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
/// @brief Class that acts as a UnixCommandSocket client
|
||||||
|
///
|
||||||
|
/// This class is expected to be used unit-tests that attempt to communicate
|
||||||
|
/// with the servers that use control channel (see src/lib/config/command_mgr.h)
|
||||||
|
/// It can connect to an open UnixCommandSocket and exchange ControlChannel
|
||||||
|
/// commands and responses.
|
||||||
|
class UnixControlClient {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// @brief Default constructor
|
||||||
|
UnixControlClient();
|
||||||
|
|
||||||
|
/// @brief Destructor
|
||||||
|
~UnixControlClient();
|
||||||
|
|
||||||
|
/// @brief Closes the Control Channel socket
|
||||||
|
void disconnectFromServer();
|
||||||
|
|
||||||
|
/// @brief Connects to a Unix socket at the given path
|
||||||
|
/// @param socket_path pathname of the socket to open
|
||||||
|
/// @return true if the connect was successful, false otherwise
|
||||||
|
bool connectToServer(const std::string& socket_path);
|
||||||
|
|
||||||
|
/// @brief Sends the given command across the open Control Channel
|
||||||
|
/// @param command the command text to execute in JSON form
|
||||||
|
/// @return true if the send succeeds, false otherwise
|
||||||
|
bool sendCommand(const std::string& command);
|
||||||
|
|
||||||
|
/// @brief Reads the response text from the open Control Channel
|
||||||
|
/// @param response variable into which the received response should be
|
||||||
|
/// placed.
|
||||||
|
/// @return true if data was successfully read from the socket,
|
||||||
|
/// false otherwise
|
||||||
|
bool getResponse(std::string& response);
|
||||||
|
|
||||||
|
/// @brief Uses select to poll the Control Channel for data waiting
|
||||||
|
/// @return -1 on error, 0 if no data is available, 1 if data is ready
|
||||||
|
int selectCheck();
|
||||||
|
|
||||||
|
/// @brief Retains the fd of the open socket
|
||||||
|
int socket_fd_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // end of isc::dhcp::test namespace
|
||||||
|
}; // end of isc::dhcp namespace
|
||||||
|
}; // end of isc namespace
|
||||||
|
|
||||||
|
#endif // UNIX_CONTROL_CLIENT_H
|
Loading…
x
Reference in New Issue
Block a user