2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-01 22:45:18 +00:00

[#260,!20] Packet queueing is now optional

src/bin/dhcp<4/6>/ctrl_dhcp<4/6>_srv.cc
    ControlledDhcpv<4/6>Srv::processConfig() - now calls
        IfaceMgr::configureDHCPPacketQueue

src/bin/dhcp<4/6>/dhcp<4/6>_parser.yy
    dhpc-queue-control parsing updated to enforce
    enable-queue/queue-type rules

src/bin/dhcp<4/6>/tests/config_parser_unittest.cc
    TEST_F(Dhcp<4/6>ParserTest, dhcpQueueControl)
    TEST_F(Dhcp<4/6>ParserTest, dhcpQueueControlInvalid)

src/lib/dhcp/iface_mgr.*
    IfaceMgr
    - closeSockets()  - now calls stopDHCPReceiver

    - openSockets<4/6>() -  now calls startDHCPReceiver

    - receive<4/6>Indirect() - new function which monitors receiver
    thread watch sockets, reads DHCP packets from queue

    - receive<4/6>Direct() - new function which monitors and reads DHCP
    packets from interface sockets directly

    - receive<4/6>() - rewritten to call receive<4/6>Indirect
    if receiver thread is running, otherwise it calls receive<4/6>Direct

    - configureDHCPPacketQueue() - new function which either enables queuing
    by creating a new packet queue, or disables it by destroying the
    existing queue

src/lib/dhcp/packet_queue_mgr.h
    PacketQueue::destroyPacketQueue() - new function

src/lib/dhcp/packet_queue_mgr<4/6>.cc
    PacketQueueMgr<4/6>::PacketQueueMgr<4/6>() - no longer creates a
    default packet queue

src/lib/dhcpsrv/cfg_iface.cc
    CfgIface::closeSockets() - removed call to stopDHCPReceiver
    CfgIface::openSockets() - removed call to startDHCPReceiver

src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.*
    DHCPQueueControlParser
    - removed unused family_ member
    - parse() - added support for enable-queue

src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc
    - new file
This commit is contained in:
Thomas Markwalder
2018-11-10 13:20:22 -05:00
parent 2f8233824d
commit bbb822b115
23 changed files with 783 additions and 175 deletions

View File

@@ -634,23 +634,14 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
return (isc::config::createAnswer(1, err.str()));
}
// Configure packet queue
// Configure DHCP packet queueing
try {
data::ConstElementPtr qc;
qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
if (!qc) {
// @todo For now we're manually constructing default queue config
// This probably needs to be built into the PQM?
data::ElementPtr default_qc = data::Element::createMap();
default_qc->set("queue-type", data::Element::create("kea-ring4"));
default_qc->set("capacity", data::Element::create(static_cast<long int>(500)));
PacketQueueMgr4::instance().createPacketQueue(default_qc);
} else {
PacketQueueMgr4::instance().createPacketQueue(qc);
}
if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CONFIG_PACKET_QUEUE)
.arg(PacketQueueMgr4::instance().getPacketQueue()->getInfoStr());
}
} catch (const std::exception& ex) {
err << "Error setting packet queue controls after server reconfiguration: "

View File

@@ -1844,13 +1844,40 @@ dhcp_queue_control: DHCP_QUEUE_CONTROL {
ElementPtr qc = $4;
ctx.stack_.back()->set("dhcp-queue-control", qc);
if (!qc->contains("queue-type")) {
// Doing this manually, because dhcp-queue-control
// content is otherwise arbitrary
if (!qc->contains("enable-queue")) {
std::stringstream msg;
msg << "'queue-type' is required: ";
msg << "'enable-queue' is required: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
ConstElementPtr enable_queue = qc->get("enable-queue");
if (enable_queue->getType() != Element::boolean) {
std::stringstream msg;
msg << "'enable-queue' must be boolean: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
if (enable_queue->boolValue()) {
if (!qc->contains("queue-type")) {
std::stringstream msg;
msg << "'queue-type' is required, when 'enable-queue' is true: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
ConstElementPtr queue_type = qc->get("queue-type");
if (queue_type->getType() != Element::string) {
std::stringstream msg;
msg << "'queue-type' must be a string: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
}
ctx.leave();
};

View File

@@ -391,7 +391,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
}
if (config_pair.first == "dhcp-queue-control") {
DHCPQueueControlParser parser(AF_INET);
DHCPQueueControlParser parser;
srv_cfg->setDHCPQueueControl(parser.parse(config_pair.second));
continue;
}

View File

@@ -6488,19 +6488,32 @@ TEST_F(Dhcp4ParserTest, dhcpQueueControl) {
""
},
{
"valid entry",
"queue disabled",
"{ \n"
" \"queue-type\": \"some-type\", \n"
" \"capacity\": 75 \n"
" \"enable-queue\": false \n"
"} \n"
},
{
"valid arbitrary content",
"queue disabled, arbitrary content allowed",
"{ \n"
" \"enable-queue\": false, \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
},
{
"queue enabled, with queue-type",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\" \n"
"} \n"
},
{
"queue enabled with queue-type and arbitrary content",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\", \n"
" \"capacity\": 90, \n"
" \"user-context\": { \"comment\": \"some text\" },\n"
" \"random-bool\" : false, \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
}
@@ -6511,9 +6524,7 @@ TEST_F(Dhcp4ParserTest, dhcpQueueControl) {
control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
ASSERT_FALSE(control);
// Iterate over the incorrect scenarios and verify they
// fail as expected. Note, we use parseDHCP4() directly
// as all of the errors above are enforced by the grammar.
// Iterate over the valid scenarios and verify they succeed.
data::ConstElementPtr exp_elems;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
@@ -6558,24 +6569,43 @@ TEST_F(Dhcp4ParserTest, dhcpQueueControlInvalid) {
struct Scenario {
std::string description_;
std::string json_;
std::string exp_error_;
};
std::vector<Scenario> scenarios = {
{
"not a map",
"{ " + genIfaceConfig() + ", \n" +
" \"subnet4\": [ ], \n"
" \"dhcp-queue-control\": 75 \n"
"} \n"
"75 \n",
"<string>:2.24-25: syntax error, unexpected integer, expecting {"
},
{
"queue type missing",
"{ " + genIfaceConfig() + ", \n" +
" \"subnet4\": [ ], \n"
" \"dhcp-queue-control\": { \n"
" \"capacity\": 100 \n"
" } \n"
"} \n"
"enable-queue missing",
"{ \n"
" \"enable-type\": \"some-type\" \n"
"} \n",
"<string>:2.2-21: 'enable-queue' is required: <string>:2:24)"
},
{
"enable-queue not boolean",
"{ \n"
" \"enable-queue\": \"always\" \n"
"} \n",
"<string>:2.2-21: 'enable-queue' must be boolean: <string>:2:24)"
},
{
"queue enabled, type missing",
"{ \n"
" \"enable-queue\": true \n"
"} \n",
"<string>:2.2-21: 'queue-type' is required, when 'enable-queue' is true: <string>:2:24)"
},
{
"queue enabled, type not a string",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": 7777 \n"
"} \n",
"<string>:2.2-21: 'queue-type' must be a string: <string>:2:24)"
}
};
@@ -6585,10 +6615,23 @@ TEST_F(Dhcp4ParserTest, dhcpQueueControlInvalid) {
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
EXPECT_THROW(parseDHCP4(scenario.json_), Dhcp4ParseError);
// Construct the config JSON
std::stringstream os;
os << "{ " + genIfaceConfig();
os << ",\n \"dhcp-queue-control\": " << scenario.json_;
os << "} \n";
std::string error_msg = "";
try {
ASSERT_TRUE(parseDHCP4(os.str(), false)) << "parser returned empty element";
} catch(const std::exception& ex) {
error_msg = ex.what();
}
ASSERT_FALSE(error_msg.empty()) << "parseDHCP4 should have thrown";
EXPECT_EQ(scenario.exp_error_, error_msg);
}
}
}
}

View File

@@ -653,27 +653,18 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
return (isc::config::createAnswer(1, err.str()));
}
// Configure DHCP packet queue
// Configure DHCP packet queueing
try {
data::ConstElementPtr qc;
qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
if (!qc) {
// @todo For now we're manually constructing default queue config
// This probably needs to be built into the PQM?
data::ElementPtr default_qc = data::Element::createMap();
default_qc->set("queue-type", data::Element::create("kea-ring6"));
default_qc->set("capacity", data::Element::create(static_cast<long int>(500)));
PacketQueueMgr6::instance().createPacketQueue(default_qc);
} else {
PacketQueueMgr6::instance().createPacketQueue(qc);
}
if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, qc)) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CONFIG_PACKET_QUEUE)
.arg(PacketQueueMgr6::instance().getPacketQueue()->getInfoStr());
}
} catch (const std::exception& ex) {
std::ostringstream err;
err << "Error setting DHCP packet queue controls after server reconfiguration: "
err << "Error setting packet queue controls after server reconfiguration: "
<< ex.what();
return (isc::config::createAnswer(1, err.str()));
}

View File

@@ -1933,13 +1933,41 @@ dhcp_queue_control: DHCP_QUEUE_CONTROL {
ElementPtr qc = $4;
ctx.stack_.back()->set("dhcp-queue-control", qc);
if (!qc->contains("queue-type")) {
// Doing this manually, because dhcp-queue-control
// content is otherwise arbitrary
if (!qc->contains("enable-queue")) {
std::stringstream msg;
msg << "'queue-type' is required: ";
msg << "'enable-queue' is required: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
ConstElementPtr enable_queue = qc->get("enable-queue");
if (enable_queue->getType() != Element::boolean) {
std::stringstream msg;
msg << "'enable-queue' must be boolean: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
if (enable_queue->boolValue()) {
if (!qc->contains("queue-type")) {
std::stringstream msg;
msg << "'queue-type' is required, when 'enable-queue' is true: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
ConstElementPtr queue_type = qc->get("queue-type");
if (queue_type->getType() != Element::string) {
std::stringstream msg;
msg << "'queue-type' must be a string: ";
msg << qc->getPosition().str() << ")";
error(@1, msg.str());
}
}
ctx.leave();
};

View File

@@ -484,7 +484,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
}
if (config_pair.first == "dhcp-queue-control") {
DHCPQueueControlParser parser(AF_INET);
DHCPQueueControlParser parser;
srv_config->setDHCPQueueControl(parser.parse(config_pair.second));
continue;
}

View File

@@ -6993,20 +6993,33 @@ TEST_F(Dhcp6ParserTest, dhcpQueueControl) {
""
},
{
"valid entry",
"queue disabled",
"{ \n"
" \"queue-type\": \"some-type\", \n"
" \"capacity\": 75 \n"
" \"enable-queue\": false \n"
"} \n"
},
{
"valid arbitrary content",
"queue disabled, arbitrary content allowed",
"{ \n"
" \"enable-queue\": false, \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
},
{
"queue enabled, with queue-type",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\" \n"
"} \n"
},
{
"queue enabled with queue-type and arbitrary content",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\", \n"
" \"capacity\": 90, \n"
" \"user-context\": { \"comment\": \"some text\" },\n"
" \"random-bool\" : false, \n"
" \"random-int\" : 1236 \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
}
};
@@ -7016,9 +7029,7 @@ TEST_F(Dhcp6ParserTest, dhcpQueueControl) {
control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
ASSERT_FALSE(control);
// Iterate over the incorrect scenarios and verify they
// fail as expected. Note, we use parseDHCP6() directly
// as all of the errors above are enforced by the grammar.
// Iterate over the valid scenarios and verify they succeed.
data::ConstElementPtr exp_elems;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
@@ -7063,24 +7074,43 @@ TEST_F(Dhcp6ParserTest, dhcpQueueControlInvalid) {
struct Scenario {
std::string description_;
std::string json_;
std::string exp_error_;
};
std::vector<Scenario> scenarios = {
{
"not a map",
"{ " + genIfaceConfig() + ", \n" +
" \"subnet6\": [ ], \n"
" \"dhcp-queue-control\": 75 \n"
"} \n"
"75 \n",
"<string>:2.24-25: syntax error, unexpected integer, expecting {"
},
{
"queue type missing",
"{ " + genIfaceConfig() + ", \n" +
" \"subnet6\": [ ], \n"
" \"dhcp-queue-control\": { \n"
" \"capacity\": 100 \n"
" } \n"
"} \n"
"enable-queue missing",
"{ \n"
" \"enable-type\": \"some-type\" \n"
"} \n",
"<string>:2.2-21: 'enable-queue' is required: <string>:2:24)"
},
{
"enable-queue not boolean",
"{ \n"
" \"enable-queue\": \"always\" \n"
"} \n",
"<string>:2.2-21: 'enable-queue' must be boolean: <string>:2:24)"
},
{
"queue enabled, type missing",
"{ \n"
" \"enable-queue\": true \n"
"} \n",
"<string>:2.2-21: 'queue-type' is required, when 'enable-queue' is true: <string>:2:24)"
},
{
"queue enabled, type not a string",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": 7777 \n"
"} \n",
"<string>:2.2-21: 'queue-type' must be a string: <string>:2:24)"
}
};
@@ -7090,7 +7120,21 @@ TEST_F(Dhcp6ParserTest, dhcpQueueControlInvalid) {
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
EXPECT_THROW(parseDHCP6(scenario.json_), Dhcp6ParseError);
// Construct the config JSON
std::stringstream os;
os << "{ " + genIfaceConfig();
os << ",\n \"dhcp-queue-control\": " << scenario.json_;
os << "} \n";
std::string error_msg = "";
try {
ASSERT_TRUE(parseDHCP6(os.str(), false)) << "parser returned empty element";
} catch(const std::exception& ex) {
error_msg = ex.what();
}
ASSERT_FALSE(error_msg.empty()) << "parseDHCP6 should have thrown";
EXPECT_EQ(scenario.exp_error_, error_msg);
}
}
}

View File

@@ -93,6 +93,7 @@ Iface::closeSockets(const uint16_t family) {
<< " specified when requested to close all sockets"
<< " which belong to this family");
}
// Search for the socket of the specific type.
SocketCollection::iterator sock = sockets_.begin();
while (sock != sockets_.end()) {
@@ -282,6 +283,9 @@ Iface::countActive4() const {
}
void IfaceMgr::closeSockets() {
// Stops the receiver thread if there is one.
stopDHCPReceiver();
BOOST_FOREACH(IfacePtr iface, ifaces_) {
iface->closeSockets();
}
@@ -293,11 +297,17 @@ void IfaceMgr::stopDHCPReceiver() {
receiver_thread_->wait();
receiver_thread_.reset();
error_watch_.clearReady();
}
receiver_error_ = "no error";
if (getPacketQueue4()) {
getPacketQueue4()->clear();
getPacketQueue4()->clear();
}
if (getPacketQueue6()) {
getPacketQueue6()->clear();
}
}
IfaceMgr::~IfaceMgr() {
@@ -584,6 +594,12 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
}
}
if (count > 0) {
// starts the receiver thread (if queueing is enabled);
startDHCPReceiver(AF_INET);
}
return (count > 0);
}
@@ -662,6 +678,11 @@ IfaceMgr::openSockets6(const uint16_t port,
}
}
if (count > 0) {
// starts the receiver thread (if queueing is enabled);
startDHCPReceiver(AF_INET6);
}
return (count > 0);
}
@@ -674,14 +695,14 @@ IfaceMgr::startDHCPReceiver(const uint16_t family) {
switch (family) {
case AF_INET:
if(!getPacketQueue4()) {
isc_throw(Unexpected, "startDHCPRecever - no packet queue?");
return;
}
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP4Packets, this)));
break;
case AF_INET6:
if(!getPacketQueue6()) {
isc_throw(Unexpected, "startDHCPRecever - no packet queue?");
return;
}
receiver_thread_.reset(new Thread(boost::bind(&IfaceMgr::receiveDHCP6Packets, this)));
@@ -942,8 +963,15 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
return (packet_filter_->send(*iface, getSocket(*pkt).sockfd_, pkt));
}
Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
if (receiver_thread_) {
return (receive4Indirect(timeout_sec, timeout_usec));
}
return (receive4Direct(timeout_sec, timeout_usec));
}
Pkt4Ptr IfaceMgr::receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
@@ -1051,7 +1079,233 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
return (pkt);
}
Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
" one million microseconds");
}
boost::scoped_ptr<SocketInfo> candidate;
IfacePtr iface;
fd_set sockets;
int maxfd = 0;
FD_ZERO(&sockets);
/// @todo: marginal performance optimization. We could create the set once
/// and then use its copy for select(). Please note that select() modifies
/// provided set to indicated which sockets have something to read.
BOOST_FOREACH(iface, ifaces_) {
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
// Only deal with IPv4 addresses.
if (s.addr_.isV4()) {
// Add this socket to listening set
FD_SET(s.sockfd_, &sockets);
if (maxfd < s.sockfd_) {
maxfd = s.sockfd_;
}
}
}
}
// if there are any callbacks for external sockets registered...
if (!callbacks_.empty()) {
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
FD_SET(s.socket_, &sockets);
if (maxfd < s.socket_) {
maxfd = s.socket_;
}
}
}
struct timeval select_timeout;
select_timeout.tv_sec = timeout_sec;
select_timeout.tv_usec = timeout_usec;
// zero out the errno to be safe
errno = 0;
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
if (result == 0) {
// nothing received and timeout has been reached
return (Pkt4Ptr()); // NULL
} else if (result < 0) {
// In most cases we would like to know whether select() returned
// an error because of a signal being received or for some other
// reason. This is because DHCP servers use signals to trigger
// certain actions, like reconfiguration or graceful shutdown.
// By catching a dedicated exception the caller will know if the
// error returned by the function is due to the reception of the
// signal or for some other reason.
if (errno == EINTR) {
isc_throw(SignalInterruptOnSelect, strerror(errno));
} else {
isc_throw(SocketReadError, strerror(errno));
}
}
// Let's find out which socket has the data
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
if (!FD_ISSET(s.socket_, &sockets)) {
continue;
}
// something received over external socket
// Calling the external socket's callback provides its service
// layer access without integrating any specific features
// in IfaceMgr
if (s.callback_) {
s.callback_();
}
return (Pkt4Ptr());
}
// Let's find out which interface/socket has the data
BOOST_FOREACH(iface, ifaces_) {
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
if (FD_ISSET(s.sockfd_, &sockets)) {
candidate.reset(new SocketInfo(s));
break;
}
}
if (candidate) {
break;
}
}
if (!candidate) {
isc_throw(SocketReadError, "received data over unknown socket");
}
// Now we have a socket, let's get some data from it!
// Assuming that packet filter is not NULL, because its modifier checks it.
return (packet_filter_->receive(*iface, *candidate));
}
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
if (receiver_thread_) {
return (receive6Indirect(timeout_sec, timeout_usec));
}
return (receive6Direct(timeout_sec, timeout_usec));
}
Pkt6Ptr IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
" one million microseconds");
}
boost::scoped_ptr<SocketInfo> candidate;
fd_set sockets;
int maxfd = 0;
FD_ZERO(&sockets);
/// @todo: marginal performance optimization. We could create the set once
/// and then use its copy for select(). Please note that select() modifies
/// provided set to indicated which sockets have something to read.
BOOST_FOREACH(IfacePtr iface, ifaces_) {
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
// Only deal with IPv6 addresses.
if (s.addr_.isV6()) {
// Add this socket to listening set
FD_SET(s.sockfd_, &sockets);
if (maxfd < s.sockfd_) {
maxfd = s.sockfd_;
}
}
}
}
// if there are any callbacks for external sockets registered...
if (!callbacks_.empty()) {
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
// Add it to the set as well
FD_SET(s.socket_, &sockets);
if (maxfd < s.socket_) {
maxfd = s.socket_;
}
}
}
struct timeval select_timeout;
select_timeout.tv_sec = timeout_sec;
select_timeout.tv_usec = timeout_usec;
// zero out the errno to be safe
errno = 0;
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
if (result == 0) {
// nothing received and timeout has been reached
return (Pkt6Ptr()); // NULL
} else if (result < 0) {
// In most cases we would like to know whether select() returned
// an error because of a signal being received or for some other
// reason. This is because DHCP servers use signals to trigger
// certain actions, like reconfiguration or graceful shutdown.
// By catching a dedicated exception the caller will know if the
// error returned by the function is due to the reception of the
// signal or for some other reason.
if (errno == EINTR) {
isc_throw(SignalInterruptOnSelect, strerror(errno));
} else {
isc_throw(SocketReadError, strerror(errno));
}
}
// Let's find out which socket has the data
BOOST_FOREACH(SocketCallbackInfo s, callbacks_) {
if (!FD_ISSET(s.socket_, &sockets)) {
continue;
}
// something received over external socket
// Calling the external socket's callback provides its service
// layer access without integrating any specific features
// in IfaceMgr
if (s.callback_) {
s.callback_();
}
return (Pkt6Ptr());
}
// Let's find out which interface/socket has the data
BOOST_FOREACH(IfacePtr iface, ifaces_) {
BOOST_FOREACH(SocketInfo s, iface->getSockets()) {
if (FD_ISSET(s.sockfd_, &sockets)) {
candidate.reset(new SocketInfo(s));
break;
}
}
if (candidate) {
break;
}
}
if (!candidate) {
isc_throw(SocketReadError, "received data over unknown socket");
}
// Assuming that packet filter is not NULL, because its modifier checks it.
return (packet_filter6_->receive(*candidate));
}
Pkt6Ptr IfaceMgr::receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
@@ -1485,5 +1739,42 @@ IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
return (*candidate);
}
bool
IfaceMgr::configureDHCPPacketQueue(uint16_t family, data::ConstElementPtr queue_control) {
if (receiver_thread_) {
isc_throw(InvalidOperation, "Cannot reconfigure queueing"
" while receiver thread is running");
}
bool enable_queue = false;
if (queue_control) {
try {
enable_queue = data::SimpleParser::getBoolean(queue_control, "enable-queue");
} catch (...) {
// @todo - for now swallow not found errors.
// if not present we assume default
}
}
if (enable_queue) {
// Try to create the queue as configured.
if (family == AF_INET) {
PacketQueueMgr4::instance().createPacketQueue(queue_control);
} else {
PacketQueueMgr6::instance().createPacketQueue(queue_control);
}
} else {
// Destroy the current queue (if one), this inherently disables threading.
if (family == AF_INET) {
PacketQueueMgr4::instance().destroyPacketQueue();
} else {
PacketQueueMgr6::instance().destroyPacketQueue();
}
}
return(enable_queue);
}
} // end of namespace isc::dhcp
} // end of namespace isc

View File

@@ -699,6 +699,9 @@ public:
/// @return Pkt6 object representing received packet (or NULL)
Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0);
Pkt6Ptr receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
/// @brief Tries to receive IPv4 packet over open IPv4 sockets.
///
/// Attempts to receive a single DHCPv4 message over any of the open
@@ -721,6 +724,9 @@ public:
/// @return Pkt4 object representing received packet (or NULL)
Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0);
Pkt4Ptr receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
Pkt4Ptr receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
/// Opens UDP/IP socket and binds it to address, interface and port.
///
/// Specific type of socket (UDP/IPv4 or UDP/IPv6) depends on passed addr
@@ -808,6 +814,9 @@ public:
/// but it is not running, it is down, or is a loopback interface when
/// loopback is not allowed, an error is reported.
///
/// If sockets were successfully opened, it calls @ startDHCPReceiver to
/// start the receiver thread (if packet queueing is enabled).
///
/// On the systems with multiple interfaces, it is often desired that the
/// failure to open a socket on a particular interface doesn't cause a
/// fatal error and sockets should be opened on remaining interfaces.
@@ -857,6 +866,9 @@ public:
/// represented by a class derived from @c isc::dhcp::PktFilter abstract
/// class.
///
/// If sockets were successfully opened, it calls @ startDHCPReceiver to
/// start the receiver thread (if packet queueing is enabled).
///
/// It is possible to specify whether sockets should be broadcast capable.
/// In most of the cases, the sockets should support broadcast traffic, e.g.
/// DHCPv4 server and relay need to listen to broadcast messages sent by
@@ -916,6 +928,10 @@ public:
IfaceMgrErrorMsgCallback error_handler = 0);
/// @brief Closes all open sockets.
///
/// It calls @c stopDHCPReceiver to stop the receiver thread and then
/// it closes all open interface sockets.
///
/// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
void closeSockets();
@@ -1047,7 +1063,8 @@ public:
/// @brief Starts DHCP packet receiver.
///
/// Starts the DHCP packet receiver thread for the given.
/// protocol, AF_NET or AF_INET6
/// protocol, AF_NET or AF_INET6, if the packet queue
/// exists, otherwise it simply returns.
///
/// @param family indicates which receiver to start,
/// (AF_INET or AF_INET6)
@@ -1057,9 +1074,27 @@ public:
/// @brief Stops the DHCP packet receiver.
///
/// Stops the receiver and deletes the dedicated thread.
/// If the thread exists, it is stopped, deleted, and
/// the packet queue is flushed.
void stopDHCPReceiver();
/// @brief Configures DHCP packet queue
///
/// If the given configuration enables packet queueing, then the
/// appropriate queue is created. Otherwise, the existing queue is
/// destroyed. If the receiver thread is running when this function
/// is invoked, it will throw.
///
/// @param family indicates which receiver to start,
/// (AF_INET or AF_INET6)
/// @parm queue_control configuration containing "dhcp-queue-control"
/// content
/// @return true if packet queueuing has been enabled, false otherwise
/// @throw InvalidOperation if the receiver thread is currently running.
bool configureDHCPPacketQueue(const uint16_t family,
data::ConstElementPtr queue_control);
// don't use private, we need derived classes in tests
protected:

View File

@@ -158,6 +158,12 @@ public:
return (packet_queue_);
}
/// @brief Destroys the current packet queue.
/// Any queued packets will be discarded.
void destroyPacketQueue() {
packet_queue_.reset();
}
protected:
/// @brief A map holding registered backend factory functions.
std::map<std::string, Factory> factories_;

View File

@@ -30,11 +30,6 @@ PacketQueueMgr4::PacketQueueMgr4() {
PacketQueue4Ptr queue(new PacketQueueRing4("kea-ring4", capacity));
return (queue);
});
data::ElementPtr parameters = data::Element::createMap();
parameters->set("queue-type", data::Element::create("kea-ring4"));
parameters->set("capacity", data::Element::create(static_cast<long int>(500)));
createPacketQueue(parameters);
}
boost::scoped_ptr<PacketQueueMgr4>&

View File

@@ -55,8 +55,7 @@ public:
private:
/// @brief Private constructor.
///
/// It registers a default factory for DHCPv4 queues and creates
/// an default DHCPv4 packet queue.
/// It registers a default factory for DHCPv4 queues.
PacketQueueMgr4();
/// @brief Returns a pointer to the currently instance of the

View File

@@ -30,11 +30,6 @@ PacketQueueMgr6::PacketQueueMgr6() {
PacketQueue6Ptr queue(new PacketQueueRing6("kea-ring6", capacity));
return (queue);
});
data::ElementPtr parameters = data::Element::createMap();
parameters->set("queue-type", data::Element::create("kea-ring6"));
parameters->set("capacity", data::Element::create(static_cast<long int>(500)));
createPacketQueue(parameters);
}
boost::scoped_ptr<PacketQueueMgr6>&

View File

@@ -56,8 +56,7 @@ public:
private:
/// @brief Private constructor.
///
/// It registers a default factory for DHCPv6 queues and creates
/// an default DHCPv6 packet queue.
/// It registers a default factory for DHCPv6 queues.
PacketQueueMgr6();
/// @brief Returns a pointer to the currently used instance of the

View File

@@ -645,12 +645,14 @@ TEST_F(IfaceMgrTest, clearIfaces) {
TEST_F(IfaceMgrTest, packetQueue4) {
NakedIfaceMgr ifacemgr;
// Get the default queue.
PacketQueue4Ptr q4 = ifacemgr.getPacketQueue4();
ASSERT_TRUE(q4);
// Should not have a queue at start up.
ASSERT_FALSE(ifacemgr.getPacketQueue4());
// Verify that the queue is what we expect.
checkInfo(q4, "{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring4", 2000);
ASSERT_NO_THROW(PacketQueueMgr4::instance().createPacketQueue(config));
checkInfo(ifacemgr.getPacketQueue4(),
"{ \"capacity\": 2000, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Verify that fetching the queue via IfaceMgr and PacketQueueMgr
// returns the same queue.
@@ -661,18 +663,20 @@ TEST_F(IfaceMgrTest, packetQueue4) {
TEST_F(IfaceMgrTest, packetQueue6) {
NakedIfaceMgr ifacemgr;
// Get the default queue.
PacketQueue6Ptr q6 = ifacemgr.getPacketQueue6();
// Should not have a queue at start up.
ASSERT_FALSE(ifacemgr.getPacketQueue6());
// Verify that we have a default queue and its info is correct.
checkInfo(q6, "{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring6", 2000);
ASSERT_NO_THROW(PacketQueueMgr6::instance().createPacketQueue(config));
checkInfo(ifacemgr.getPacketQueue6(),
"{ \"capacity\": 2000, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Verify that fetching the queue via IfaceMgr and PacketQueueMgr
// returns the same queue.
ASSERT_EQ(ifacemgr.getPacketQueue6(), PacketQueueMgr6::instance().getPacketQueue());
}
TEST_F(IfaceMgrTest, receiveTimeout6) {
using namespace boost::posix_time;
std::cout << "Testing DHCPv6 packet reception timeouts."
@@ -732,7 +736,7 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
TEST_F(IfaceMgrTest, receiveTimeout4) {
using namespace boost::posix_time;
std::cout << "Testing DHCPv6 packet reception timeouts."
std::cout << "Testing DHCPv4 packet reception timeouts."
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
@@ -745,6 +749,7 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
);
// Socket is open if returned value is non-negative.
ASSERT_GE(socket1, 0);
// Start receiver.
ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
@@ -780,8 +785,8 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
EXPECT_LE(duration.total_microseconds(), 700000);
// Test with invalid fractional timeout values.
EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue);
EXPECT_THROW(ifacemgr->receive6(2, 1000005), isc::BadValue);
EXPECT_THROW(ifacemgr->receive4(0, 1000000), isc::BadValue);
EXPECT_THROW(ifacemgr->receive4(2, 1000005), isc::BadValue);
// Stop receiver.
EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver());
@@ -1129,7 +1134,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_GE(socket1, 0);
#if 0
ifacemgr->startDHCPReceiver(AF_INET);
#endif
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
@@ -1213,7 +1220,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
#if 0
ifacemgr->stopDHCPReceiver();
#endif
}
// Verifies that it is possible to set custom packet filter object

View File

@@ -96,23 +96,20 @@ public:
};
// Verifies that DHCPv4 PQM provides a default queue factory
// and packet queue.
TEST_F(PacketQueueMgr4Test, defaultQueue) {
// Should not be a queue at start-up
ASSERT_FALSE(mgr().getPacketQueue());
// Verify that we have a default queue and its info is correct.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring4", 2000);
// Verify that we can replace the default queue with different capacity queue
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// We should be able to recreate the manager.
ASSERT_NO_THROW(PacketQueueMgr4::create());
// And be back to having the default queue.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Should not be a queue.
ASSERT_FALSE(mgr().getPacketQueue());
}
// Verifies that PQM registry and creation of custome queue implementations.
@@ -125,10 +122,7 @@ TEST_F(PacketQueueMgr4Test, customQueueType) {
// Register our adjustable-type factory
ASSERT_TRUE(addCustomQueueType("custom-queue"));
// We still have our default queue.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
// Verify that we can replace the default queue with a "custom-queue" queue
// Verify that we can create a custom queue.
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");

View File

@@ -84,23 +84,20 @@ public:
};
// Verifies that DHCPv6 PQM provides a default queue factory
// and packet queue.
TEST_F(PacketQueueMgr6Test, defaultQueue) {
// Should not be a queue at start-up
ASSERT_FALSE(mgr().getPacketQueue());
// Verify that we have a default queue and its info is correct.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Verify that we can create a queue with default factory.
data::ConstElementPtr config = makeQueueConfig("kea-ring6", 2000);
// Verify that we can replace the default queue with different capacity queue
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// We should be able to recreate the manager.
ASSERT_NO_THROW(PacketQueueMgr6::create());
// And be back to having the default queue.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Should not be a queue.
ASSERT_FALSE(mgr().getPacketQueue());
}
// Verifies that PQM registry and creation of custome queue implementations.
@@ -113,10 +110,7 @@ TEST_F(PacketQueueMgr6Test, customQueueType) {
// Register our adjustable-type factory
ASSERT_TRUE(addCustomQueueType("custom-queue"));
// We still have our default queue.
checkMyInfo("{ \"capacity\": 500, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
// Verify that we can replace the default queue with a "custom-queue" queue
// Verify that we can create a custom queue.
ASSERT_NO_THROW(mgr().createPacketQueue(config));
checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");

View File

@@ -28,7 +28,6 @@ CfgIface::CfgIface()
void
CfgIface::closeSockets() const {
IfaceMgr::instance().stopDHCPReceiver();
IfaceMgr::instance().closeSockets();
}
@@ -174,11 +173,7 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
sopen = IfaceMgr::instance().openSockets6(port, error_callback);
}
if (sopen) {
// @todo we may consider starting/stopping this when DHCP service is
// enable/disabled, rather then when we open sockets.
IfaceMgr::instance().startDHCPReceiver(family);
} else {
if (!sopen) {
// If no socket were opened, log a warning because the server will
// not respond to any queries.
LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);

View File

@@ -18,15 +18,6 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
DHCPQueueControlParser::DHCPQueueControlParser(const uint16_t family)
: family_(family) {
// @todo Not sure we need family but just in case.
if (family_ != AF_INET && family_ != AF_INET6) {
isc_throw(BadValue, "DHCPQueueControlParser - invalid family: "
<< family_ << ", must be AF_INET or AF_INET6");
}
}
data::ElementPtr
DHCPQueueControlParser::parse(const isc::data::ConstElementPtr& control_elem) {
// All we really do here is verify that it is a map that
@@ -36,14 +27,19 @@ DHCPQueueControlParser::parse(const isc::data::ConstElementPtr& control_elem) {
isc_throw(DhcpConfigError, "dhcp-queue-control must be a map");
}
// enable-queue is mandatory.
bool enable_queue = getBoolean(control_elem, "enable-queue");
if (enable_queue) {
ConstElementPtr elem = control_elem->get("queue-type");
if (!elem) {
isc_throw(DhcpConfigError, "queue-type is required");
isc_throw(DhcpConfigError, "when queue is enabled, queue-type is required");
} else {
if (elem->getType() != Element::string) {
isc_throw(DhcpConfigError, "queue-type must be a string");
}
}
}
// Return a copy of it.
return (data::copy(control_elem));

View File

@@ -18,9 +18,16 @@ namespace dhcp {
///
/// This parser parses the "dhcp-queue-control" parameter which holds the
/// the configurable parameters that tailor DHCP packet queue behavior.
/// Currently "dhcp-queue-control" is treated as a map of arbitrary values,
/// with only one required value, "queue-type". This was done to
/// provide latitude for differing queue implementations.
/// In order to provide wide latitude to packet queue implementators,
/// 'dhcp-queue-control' is mostly treated as a map of arbitrary values.
/// There is only mandatory value, 'enable-queue', which enables/disables
/// DHCP packet queueing. If this value is true, then the content must
/// also include a value for 'queue-type'. Beyond these values, the
/// map may contain any combination of valid JSON elements.
///
/// Unlike most other parsers, this parser primarily serves to validate
/// the aforementioned rules, and rather than instantiate an object as
/// a result, it simply returns a copy original map of elements.
///
/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers
/// are not needed.
@@ -29,22 +36,19 @@ public:
/// @brief Constructor
///
/// @param family AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
explicit DHCPQueueControlParser(const uint16_t family);
DHCPQueueControlParser(){};
/// @brief Parses content of the "dhcp-queue-control".
///
/// @param values pointer to the content of parsed values
/// @param control_elem MapElement containing the queue control values to
/// parse
///
/// @return A pointer to a newly constructed DHCPQueueControl populated
/// with the parsed values
/// @return A copy of the of the input MapElement.
///
/// @throw DhcpConfigError if any of the values are invalid.
data::ElementPtr parse(const isc::data::ConstElementPtr& values);
data::ElementPtr parse(const isc::data::ConstElementPtr& control_elem);
private:
/// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
int family_;
};
}

View File

@@ -85,6 +85,7 @@ libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc
libdhcpsrv_unittests_SOURCES += dhcp_queue_control_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc
libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc

View File

@@ -0,0 +1,171 @@
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <cc/data.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::test;
namespace {
/// @brief Test fixture class for @c DHCPQueueControlParser
class DHCPQueueControlParserTest : public ::testing::Test {
protected:
/// @brief Setup for each test.
///
/// Clears the configuration in the @c CfgMgr.
virtual void SetUp();
/// @brief Cleans up after each test.
///
/// Clears the configuration in the @c CfgMgr.
virtual void TearDown();
};
void
DHCPQueueControlParserTest::SetUp() {
CfgMgr::instance().clear();
}
void
DHCPQueueControlParserTest::TearDown() {
CfgMgr::instance().clear();
}
// Verifies that DHCPQueueControlParser handles
// expected valid dhcp-queue-control contet
TEST_F(DHCPQueueControlParserTest, validContent) {
struct Scenario {
std::string description_;
std::string json_;
};
std::vector<Scenario> scenarios = {
{
"queue disabled",
"{ \n"
" \"enable-queue\": false \n"
"} \n"
},
{
"queue disabled, arbitrary content allowed",
"{ \n"
" \"enable-queue\": false, \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
},
{
"queue enabled, with queue-type",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\" \n"
"} \n"
},
{
"queue enabled with queue-type and arbitrary content",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": \"some-type\", \n"
" \"foo\": \"bogus\", \n"
" \"random-int\" : 1234 \n"
"} \n"
}
};
// Iterate over the valid scenarios and verify they succeed.
ConstElementPtr config_elems;
ConstElementPtr queue_control;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
// Construct the config JSON
ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
<< "invalid JSON, test is broken";
// Parsing config into a queue control should succeed.
DHCPQueueControlParser parser;
try {
queue_control = parser.parse(config_elems);
} catch (const std::exception& ex) {
ADD_FAILURE() << "parser threw an exception: " << ex.what();
}
// Verify the resultant queue control.
ASSERT_TRUE(queue_control);
// The parser should have created a duplicate of the
// configuration elements.
ASSERT_TRUE(queue_control.get() != config_elems.get());
EXPECT_TRUE(queue_control->equals(*config_elems));
}
}
}
// Verifies that DHCPQueueControlParser correctly catches
// invalid dhcp-queue-control content
TEST_F(DHCPQueueControlParserTest, invalidContent) {
struct Scenario {
std::string description_;
std::string json_;
};
std::vector<Scenario> scenarios = {
{
"enable-queue missing",
"{ \n"
" \"enable-type\": \"some-type\" \n"
"} \n"
},
{
"enable-queue not boolean",
"{ \n"
" \"enable-queue\": \"always\" \n"
"} \n"
},
{
"queue enabled, type missing",
"{ \n"
" \"enable-queue\": true \n"
"} \n"
},
{
"queue enabled, type not a string",
"{ \n"
" \"enable-queue\": true, \n"
" \"queue-type\": 7777 \n"
"} \n"
}
};
// Iterate over the valid scenarios and verify they succeed.
ConstElementPtr config_elems;
ConstElementPtr queue_control;
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.description_);
{
// Construct the config JSON
ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
<< "invalid JSON, test is broken";
// Parsing config into a queue control should succeed.
DHCPQueueControlParser parser;
EXPECT_THROW(parser.parse(config_elems), DhcpConfigError);
}
}
}
}; // anonymous namespace