2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 21:18:02 +00:00

[master] Merge branch 'trac5457'

# Conflicts:
#	src/bin/dhcp4/tests/callout_library_1.cc
#	src/bin/dhcp4/tests/callout_library_2.cc
This commit is contained in:
Marcin Siodelski 2018-03-06 19:19:19 +01:00
commit af43f07b0e
35 changed files with 1979 additions and 155 deletions

View File

@ -1,4 +1,4 @@
// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -15,6 +15,7 @@
#include <dhcp4/json_config_parser.h> #include <dhcp4/json_config_parser.h>
#include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_db_access.h> #include <dhcpsrv/cfg_db_access.h>
#include <hooks/hooks.h>
#include <hooks/hooks_manager.h> #include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h> #include <stats/stats_mgr.h>
#include <cfgrpt/config_report.h> #include <cfgrpt/config_report.h>
@ -30,6 +31,23 @@ using namespace std;
namespace { namespace {
/// Structure that holds registered hook indexes.
struct CtrlDhcp4Hooks {
int hooks_index_dhcp4_srv_configured_;
/// Constructor that registers hook points for the DHCPv4 server.
CtrlDhcp4Hooks() {
hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
}
};
// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
CtrlDhcp4Hooks Hooks;
/// @brief Signals handler for DHCPv4 server. /// @brief Signals handler for DHCPv4 server.
/// ///
/// This signal handler handles the following signals received by the DHCPv4 /// This signal handler handles the following signals received by the DHCPv4
@ -637,6 +655,25 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
return (isc::config::createAnswer(1, err.str())); return (isc::config::createAnswer(1, err.str()));
} }
// This hook point notifies hooks libraries that the configuration of the
// DHCPv4 server has completed. It provides the hook library with the pointer
// to the common IO service object, new server configuration in the JSON
// format and with the pointer to the configuration storage where the
// parsed configuration is stored.
if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
callout_handle->setArgument("io_context", srv->getIOService());
callout_handle->setArgument("json_config", config);
callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());
HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
*callout_handle);
// Ignore status code as none of them would have an effect on further
// operation.
}
return (answer); return (answer);
} }

View File

@ -46,6 +46,23 @@ packet processing, but the exact order depends on the actual processing. Hook po
that are not specific to packet processing (e.g. lease expiration) will be added that are not specific to packet processing (e.g. lease expiration) will be added
to the end of this list. to the end of this list.
@subsection dhcp4HooksDhcpv4SrvConfigured dhcp4_srv_configured
- @b Arguments:
- name: @b io_context, type: isc::asiolink::IOServicePtr, direction: <b>in</b>
- name: @b json_config, type: isc::data::ConstElementPtr, direction: <b>in</b>
- name: @b server_config, type: isc::dhcp::SrvConfigPtr, direction: <b>in</b>
- @b Description: this callout is executed when the server has completed
its (re)configuration. The server provides received and parsed configuration
structures to the hook library. It also provides a pointer to the IOService
object which is used by the server to run asynchronous operations. The hooks
libraries can use this IOService object to schedule asynchronous tasks which
are triggered by the DHCP server's main loop. The hook library should hold the
provided pointer until the library is unloaded.
- <b>Next step status</b>: Status codes retured by the callouts installed on
this hook point are ignored.
@subsection dhcpv4HooksBuffer4Receive buffer4_receive @subsection dhcpv4HooksBuffer4Receive buffer4_receive
- @b Arguments: - @b Arguments:
@ -215,6 +232,37 @@ to the end of this list.
marked this lease as unavailable. If the client restarts its configuration, marked this lease as unavailable. If the client restarts its configuration,
it will get the same (not declined) lease as a result. it will get the same (not declined) lease as a result.
@subsection dhcpv4Leases4Committed leases4_committed
- @b Arguments:
- name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
- name: @b leases4, type: isc::dhcp::Leases4CollectionPtr, direction: <b>in</b>
- name: @b deleted_leases4, type: isc::dhcp::Leases4CollectionPtr, direction: <b>in</b>
- @b Description: this callout is executed when the server has applied all
lease changes as a result of DHCP message processing. This includes
writing new lease into the database, releasing an old lease for this
client or declining a lease. This callout is executed only for the
DHCP client messages which may cause lease changes, i.e. DHCPREQUEST,
DHCPRELEASE and DHCPDECLINE. This callout is not executed for DHCPDISCOVER
and DHCPINFORM. If the callouts are executed as a result of DHCPREQUEST
message, it is possible that both leases collections hold leases to be
handled. This is the case when the new lease allocation replaces an existing
lease for the client. The "deleted_leases4" object will hold a previous
lease instance and the "leases4" object will hold the new lease for this
client. The callouts should be prepared to handle such situation. When
the callout is executed as a result DHCPRELEASE, the callout will typically
receive only one lease (being released) in the "deleted_leases4" object.
Both leases collections are always provided to the callouts,
even though they may sometimes be empty.
- <b>Next step status</b>: If any callout installed on the "leases4_committed"
sets the next step action to DROP the server will drop the processed query.
If it sets the next step action to PARK, the server will park the processed
packet (hold packet processing) until the hook libraries explicitly unpark
the packet after they are done performing asynchronous operations.
@subsection dhcpv4HooksPkt4Send pkt4_send @subsection dhcpv4HooksPkt4Send pkt4_send
- @b Arguments: - @b Arguments:

View File

@ -276,6 +276,14 @@ This debug message is printed when a callout installed on lease4_release
hook point set the next step status to SKIP. For this particular hook point, the hook point set the next step status to SKIP. For this particular hook point, the
value set by a callout instructs the server to not release a lease. value set by a callout instructs the server to not release a lease.
% DHCP4_HOOK_LEASES4_COMMITTED_DROP %1: packet is dropped, because a callout set the next step to DROP
This debug message is printed when a callout installed on the leases4_committed
hook point sets the next step to DROP.
% DHCP4_HOOK_LEASES4_COMMITTED_PARK %1: packet is parked, because a callout set the next step to PARK
This debug message is printed when a callout installed on the lease4_committed
hook point sets the next step to PARK.
% DHCP4_HOOK_PACKET_RCVD_SKIP %1: packet is dropped, because a callout set the next step to SKIP % DHCP4_HOOK_PACKET_RCVD_SKIP %1: packet is dropped, because a callout set the next step to SKIP
This debug message is printed when a callout installed on the pkt4_receive This debug message is printed when a callout installed on the pkt4_receive
hook point sets the next step to SKIP. For this particular hook point, the hook point sets the next step to SKIP. For this particular hook point, the

View File

@ -24,7 +24,6 @@
#include <dhcp4/dhcp4_log.h> #include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h> #include <dhcp4/dhcp4_srv.h>
#include <dhcpsrv/addr_utilities.h> #include <dhcpsrv/addr_utilities.h>
#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_host_operations.h> #include <dhcpsrv/cfg_host_operations.h>
#include <dhcpsrv/cfg_iface.h> #include <dhcpsrv/cfg_iface.h>
@ -79,22 +78,26 @@ using namespace isc::log;
using namespace isc::stats; using namespace isc::stats;
using namespace std; using namespace std;
namespace {
/// Structure that holds registered hook indexes /// Structure that holds registered hook indexes
struct Dhcp4Hooks { struct Dhcp4Hooks {
int hook_index_buffer4_receive_; ///< index for "buffer4_receive" hook point int hook_index_buffer4_receive_; ///< index for "buffer4_receive" hook point
int hook_index_pkt4_receive_; ///< index for "pkt4_receive" hook point int hook_index_pkt4_receive_; ///< index for "pkt4_receive" hook point
int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
int hook_index_leases4_committed_; ///< index for "leases4_committed" hook point
int hook_index_lease4_release_; ///< index for "lease4_release" hook point int hook_index_lease4_release_; ///< index for "lease4_release" hook point
int hook_index_pkt4_send_; ///< index for "pkt4_send" hook point int hook_index_pkt4_send_; ///< index for "pkt4_send" hook point
int hook_index_buffer4_send_; ///< index for "buffer4_send" hook point int hook_index_buffer4_send_; ///< index for "buffer4_send" hook point
int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
int hook_index_host4_identifier_;///< index for "host4_identifier" hook point int hook_index_host4_identifier_; ///< index for "host4_identifier" hook point
/// Constructor that registers hook points for DHCPv4 engine /// Constructor that registers hook points for DHCPv4 engine
Dhcp4Hooks() { Dhcp4Hooks() {
hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive"); hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive"); hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select"); hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
hook_index_leases4_committed_ = HooksManager::registerHook("leases4_committed");
hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send"); hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
hook_index_lease4_release_ = HooksManager::registerHook("lease4_release"); hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send"); hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
@ -103,12 +106,15 @@ struct Dhcp4Hooks {
} }
}; };
} // end of anonymous namespace
// Declare a Hooks object. As this is outside any function or method, it // Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded. // will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this // As a result, the hook indexes will be defined before any method in this
// module is called. // module is called.
Dhcp4Hooks Hooks; Dhcp4Hooks Hooks;
namespace isc { namespace isc {
namespace dhcp { namespace dhcp {
@ -834,72 +840,12 @@ Dhcpv4Srv::run_one() {
return; return;
} }
try {
// Now all fields and options are constructed into output wire buffer.
// Option objects modification does not make sense anymore. Hooks
// can only manipulate wire buffer at this stage.
// Let's execute all callouts registered for buffer4_send
if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
CalloutHandlePtr callout_handle = getCalloutHandle(query); CalloutHandlePtr callout_handle = getCalloutHandle(query);
processPacketBufferSend(callout_handle, rsp);
// Delete previously set arguments
callout_handle->deleteAllArguments();
// Enable copying options from the packet within hook library.
ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
// Pass incoming packet as argument
callout_handle->setArgument("response4", rsp);
// Call callouts
HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
*callout_handle);
// Callouts decided to skip the next processing step. The next
// processing step would to parse the packet, so skip at this
// stage means drop.
if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
(callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
DHCP4_HOOK_BUFFER_SEND_SKIP)
.arg(rsp->getLabel());
return;
}
callout_handle->getArgument("response4", rsp);
}
LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
.arg(rsp->getLabel())
.arg(rsp->getName())
.arg(static_cast<int>(rsp->getType()))
.arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
.arg(rsp->getLocalPort())
.arg(rsp->getRemoteAddr())
.arg(rsp->getRemotePort())
.arg(rsp->getIface().empty() ? "to be determined from routing" :
rsp->getIface());
LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
DHCP4_RESPONSE_DATA)
.arg(rsp->getLabel())
.arg(rsp->getName())
.arg(static_cast<int>(rsp->getType()))
.arg(rsp->toText());
sendPacket(rsp);
// Update statistics accordingly for sent packet.
processStatsSent(rsp);
} catch (const std::exception& e) {
LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
.arg(rsp->getLabel())
.arg(e.what());
}
} }
void void
Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) { Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) {
// Log reception of the packet. We need to increase it early, as any // Log reception of the packet. We need to increase it early, as any
// failures in unpacking will cause the packet to be dropped. We // failures in unpacking will cause the packet to be dropped. We
// will increase type specific statistic further down the road. // will increase type specific statistic further down the road.
@ -1047,6 +993,8 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
callout_handle->getArgument("query4", query); callout_handle->getArgument("query4", query);
} }
AllocEngine::ClientContext4Ptr ctx;
try { try {
switch (query->getType()) { switch (query->getType()) {
case DHCPDISCOVER: case DHCPDISCOVER:
@ -1057,15 +1005,15 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
// Note that REQUEST is used for many things in DHCPv4: for // Note that REQUEST is used for many things in DHCPv4: for
// requesting new leases, renewing existing ones and even // requesting new leases, renewing existing ones and even
// for rebinding. // for rebinding.
rsp = processRequest(query); rsp = processRequest(query, ctx);
break; break;
case DHCPRELEASE: case DHCPRELEASE:
processRelease(query); processRelease(query, ctx);
break; break;
case DHCPDECLINE: case DHCPDECLINE:
processDecline(query); processDecline(query, ctx);
break; break;
case DHCPINFORM: case DHCPINFORM:
@ -1097,6 +1045,84 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
static_cast<int64_t>(1)); static_cast<int64_t>(1));
} }
bool packet_park = false;
if (ctx && HooksManager::calloutsPresent(Hooks.hook_index_leases4_committed_)) {
CalloutHandlePtr callout_handle = getCalloutHandle(query);
// Delete all previous arguments
callout_handle->deleteAllArguments();
// Clear skip flag if it was set in previous callouts
callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
// Also pass the corresponding query packet as argument
callout_handle->setArgument("query4", query);
Lease4CollectionPtr new_leases(new Lease4Collection());
if (ctx->new_lease_) {
new_leases->push_back(ctx->new_lease_);
}
callout_handle->setArgument("leases4", new_leases);
Lease4CollectionPtr deleted_leases(new Lease4Collection());
if (ctx->old_lease_) {
if ((!ctx->new_lease_) || (ctx->new_lease_->addr_ != ctx->old_lease_->addr_)) {
deleted_leases->push_back(ctx->old_lease_);
}
}
callout_handle->setArgument("deleted_leases4", deleted_leases);
// Call all installed callouts
HooksManager::callCallouts(Hooks.hook_index_leases4_committed_,
*callout_handle);
if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
DHCP4_HOOK_LEASES4_COMMITTED_DROP)
.arg(query->getLabel());
rsp.reset();
} else if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK)
&& allow_packet_park) {
packet_park = true;
}
}
if (!rsp) {
return;
}
// PARKING SPOT after leases4_committed hook point.
CalloutHandlePtr callout_handle = getCalloutHandle(query);
if (packet_park) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
DHCP4_HOOK_LEASES4_COMMITTED_PARK)
.arg(query->getLabel());
// Park the packet. The function we bind here will be executed when the hook
// library unparks the packet.
HooksManager::park("leases4_committed", query,
[this, callout_handle, query, rsp]() mutable {
processPacketPktSend(callout_handle, query, rsp);
processPacketBufferSend(callout_handle, rsp);
});
// If we have parked the packet, let's reset the pointer to the
// response to indicate to the caller that it should return, as
// the packet processing will continue via the callback.
rsp.reset();
} else {
processPacketPktSend(callout_handle, query, rsp);
}
}
void
Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
Pkt4Ptr& query, Pkt4Ptr& rsp) {
if (!rsp) { if (!rsp) {
return; return;
} }
@ -1106,7 +1132,6 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
// Execute all callouts registered for pkt4_send // Execute all callouts registered for pkt4_send
if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) { if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
CalloutHandlePtr callout_handle = getCalloutHandle(query);
// Delete all previous arguments // Delete all previous arguments
callout_handle->deleteAllArguments(); callout_handle->deleteAllArguments();
@ -1153,6 +1178,76 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
} }
} }
void
Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
Pkt4Ptr& rsp) {
if (!rsp) {
return;
}
try {
// Now all fields and options are constructed into output wire buffer.
// Option objects modification does not make sense anymore. Hooks
// can only manipulate wire buffer at this stage.
// Let's execute all callouts registered for buffer4_send
if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
// Delete previously set arguments
callout_handle->deleteAllArguments();
// Enable copying options from the packet within hook library.
ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
// Pass incoming packet as argument
callout_handle->setArgument("response4", rsp);
// Call callouts
HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
*callout_handle);
// Callouts decided to skip the next processing step. The next
// processing step would to parse the packet, so skip at this
// stage means drop.
if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
(callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
DHCP4_HOOK_BUFFER_SEND_SKIP)
.arg(rsp->getLabel());
return;
}
callout_handle->getArgument("response4", rsp);
}
LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
.arg(rsp->getLabel())
.arg(rsp->getName())
.arg(static_cast<int>(rsp->getType()))
.arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
.arg(rsp->getLocalPort())
.arg(rsp->getRemoteAddr())
.arg(rsp->getRemotePort())
.arg(rsp->getIface().empty() ? "to be determined from routing" :
rsp->getIface());
LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
DHCP4_RESPONSE_DATA)
.arg(rsp->getLabel())
.arg(rsp->getName())
.arg(static_cast<int>(rsp->getType()))
.arg(rsp->toText());
sendPacket(rsp);
// Update statistics accordingly for sent packet.
processStatsSent(rsp);
} catch (const std::exception& e) {
LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
.arg(rsp->getLabel())
.arg(e.what());
}
}
string string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) { Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) { if (!srvid) {
@ -2440,7 +2535,7 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
} }
Pkt4Ptr Pkt4Ptr
Dhcpv4Srv::processRequest(Pkt4Ptr& request) { Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context) {
/// @todo Uncomment this (see ticket #3116) /// @todo Uncomment this (see ticket #3116)
/// sanityCheck(request, MANDATORY); /// sanityCheck(request, MANDATORY);
@ -2492,11 +2587,15 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendServerID(ex); appendServerID(ex);
// Return the pointer to the context, which will be required by the
// leases4_comitted callouts.
context = ex.getContext();
return (ex.getResponse()); return (ex.getResponse());
} }
void void
Dhcpv4Srv::processRelease(Pkt4Ptr& release) { Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context) {
/// @todo Uncomment this (see ticket #3116) /// @todo Uncomment this (see ticket #3116)
/// sanityCheck(release, MANDATORY); /// sanityCheck(release, MANDATORY);
@ -2575,6 +2674,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
bool success = LeaseMgrFactory::instance().deleteLease(lease->addr_); bool success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
if (success) { if (success) {
context.reset(new AllocEngine::ClientContext4());
context->old_lease_ = lease;
// Release successful // Release successful
LOG_INFO(lease4_logger, DHCP4_RELEASE) LOG_INFO(lease4_logger, DHCP4_RELEASE)
.arg(release->getLabel()) .arg(release->getLabel())
@ -2604,7 +2707,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
} }
void void
Dhcpv4Srv::processDecline(Pkt4Ptr& decline) { Dhcpv4Srv::processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context) {
// Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131) // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
/// @todo Uncomment this (see ticket #3116) /// @todo Uncomment this (see ticket #3116)
@ -2674,11 +2777,12 @@ Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
// Ok, all is good. The client is reporting its own address. Let's // Ok, all is good. The client is reporting its own address. Let's
// process it. // process it.
declineLease(lease, decline); declineLease(lease, decline, context);
} }
void void
Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline) { Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
AllocEngine::ClientContext4Ptr& context) {
// Let's check if there are hooks installed for decline4 hook point. // Let's check if there are hooks installed for decline4 hook point.
// If they are, let's pass the lease and client's packet. If the hook // If they are, let's pass the lease and client's packet. If the hook
@ -2742,6 +2846,9 @@ Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline) {
LeaseMgrFactory::instance().updateLease4(lease); LeaseMgrFactory::instance().updateLease4(lease);
context.reset(new AllocEngine::ClientContext4());
context->new_lease_ = lease;
LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText()) LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
.arg(decline->getLabel()).arg(lease->valid_lft_); .arg(decline->getLabel()).arg(lease->valid_lft_);
} }

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -17,6 +17,7 @@
#include <dhcp_ddns/ncr_msg.h> #include <dhcp_ddns/ncr_msg.h>
#include <dhcpsrv/alloc_engine.h> #include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfg_option.h> #include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/d2_client_mgr.h> #include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/network_state.h> #include <dhcpsrv/network_state.h>
#include <dhcpsrv/subnet.h> #include <dhcpsrv/subnet.h>
@ -25,6 +26,7 @@
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <functional>
#include <iostream> #include <iostream>
#include <queue> #include <queue>
@ -259,7 +261,10 @@ public:
/// ///
/// @param query A pointer to the packet to be processed. /// @param query A pointer to the packet to be processed.
/// @param rsp A pointer to the response /// @param rsp A pointer to the response
void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp); /// @param allow_packet_park Indicates if parking a packet is allowed.
void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp,
bool allow_packet_park = true);
/// @brief Instructs the server to shut down. /// @brief Instructs the server to shut down.
void shutdown(); void shutdown();
@ -446,9 +451,11 @@ protected:
/// Returns ACK message, NAK message, or NULL /// Returns ACK message, NAK message, or NULL
/// ///
/// @param request a message received from client /// @param request a message received from client
/// @param [out] context pointer to the client context where allocated
/// and deleted leases are stored.
/// ///
/// @return ACK or NAK message /// @return ACK or NAK message
Pkt4Ptr processRequest(Pkt4Ptr& request); Pkt4Ptr processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context);
/// @brief Processes incoming DHCPRELEASE messages. /// @brief Processes incoming DHCPRELEASE messages.
/// ///
@ -456,7 +463,9 @@ protected:
/// this function does not return anything. /// this function does not return anything.
/// ///
/// @param release message received from client /// @param release message received from client
void processRelease(Pkt4Ptr& release); /// @param [out] context pointer to the client context where released
/// lease is stored.
void processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context);
/// @brief Process incoming DHCPDECLINE messages. /// @brief Process incoming DHCPDECLINE messages.
/// ///
@ -465,7 +474,9 @@ protected:
/// the client and if it does, calls @ref declineLease. /// the client and if it does, calls @ref declineLease.
/// ///
/// @param decline message received from client /// @param decline message received from client
void processDecline(Pkt4Ptr& decline); /// @param [out] context pointer to the client context where declined
/// lease is stored.
void processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context);
/// @brief Processes incoming DHCPINFORM messages. /// @brief Processes incoming DHCPINFORM messages.
/// ///
@ -650,7 +661,9 @@ private:
/// ///
/// @param lease lease to be declined /// @param lease lease to be declined
/// @param decline client's message /// @param decline client's message
void declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline); /// @param context reference to a client context
void declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
AllocEngine::ClientContext4Ptr& context);
protected: protected:
@ -819,6 +832,21 @@ protected:
/// @param query Pointer to the client message. /// @param query Pointer to the client message.
void deferredUnpack(Pkt4Ptr& query); void deferredUnpack(Pkt4Ptr& query);
/// @brief Executes pkt4_send callout.
///
/// @param callout_handle pointer to the callout handle.
/// @param query Pointer to a query.
/// @param rsp Pointer to a response.
void processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
Pkt4Ptr& query, Pkt4Ptr& rsp);
/// @brief Executes buffer4_send callout and sends the response.
///
/// @param callout_handle pointer to the callout handle.
/// @param rsp pointer to a response.
void processPacketBufferSend(hooks::CalloutHandlePtr& callout_handle,
Pkt4Ptr& rsp);
/// @brief Allocation Engine. /// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using /// Pointer to the allocation engine that we are currently using
/// It must be a pointer, because we will support changing engines /// It must be a pointer, because we will support changing engines

View File

@ -103,7 +103,7 @@ void Dhcp4to6Ipc::handler() {
// From Dhcpv4Srv::run_one() processing and after // From Dhcpv4Srv::run_one() processing and after
Pkt4Ptr rsp; Pkt4Ptr rsp;
ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp); ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp, false);
if (!rsp) { if (!rsp) {
return; return;

View File

@ -56,7 +56,7 @@ if HAVE_GTEST
# to unexpected errors. For this reason, the --enable-static-link option is # to unexpected errors. For this reason, the --enable-static-link option is
# ignored for unit tests built here. # ignored for unit tests built here.
noinst_LTLIBRARIES = libco1.la libco2.la noinst_LTLIBRARIES = libco1.la libco2.la libco3.la
# -rpath /nowhere is a hack to trigger libtool to not create a # -rpath /nowhere is a hack to trigger libtool to not create a
# convenience archive, resulting in shared modules # convenience archive, resulting in shared modules
@ -71,6 +71,11 @@ libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
libco2_la_CPPFLAGS = $(AM_CPPFLAGS) libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
libco3_la_SOURCES = callout_library_3.cc callout_library_common.h
libco3_la_CXXFLAGS = $(AM_CXXFLAGS)
libco3_la_CPPFLAGS = $(AM_CPPFLAGS)
libco3_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
TESTS += dhcp4_unittests TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = d2_unittest.h d2_unittest.cc dhcp4_unittests_SOURCES = d2_unittest.h d2_unittest.cc

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -12,5 +12,5 @@
static const int LIBRARY_NUMBER = 1; static const int LIBRARY_NUMBER = 1;
#include <config.h> #include <config.h>
#include <dhcp4/tests/callout_library_common.h>
#include "callout_library_common.h"

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -12,5 +12,5 @@
static const int LIBRARY_NUMBER = 2; static const int LIBRARY_NUMBER = 2;
#include <config.h> #include <config.h>
#include <dhcp4/tests/callout_library_common.h>
#include "callout_library_common.h"

View File

@ -0,0 +1,44 @@
// 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/.
/// @file
/// @brief Callout library for tesing execution of the dhcp4_srv_configured
/// hook point.
///
static const int LIBRARY_NUMBER = 3;
#include <dhcp4/tests/callout_library_common.h>
#include <string>
#include <vector>
using namespace isc::hooks;
extern "C" {
/// @brief Callout which appends library number and provided arguments to
/// the marker file for dhcp4_srv_configured callout.
///
/// @param handle callout handle passed to the callout.
///
/// @return 0 on success, 1 otherwise.
int
dhcp4_srv_configured(CalloutHandle& handle) {
// Append library number.
if (appendDigit(SRV_CONFIG_MARKER_FILE)) {
return (1);
}
// Append argument names.
std::vector<std::string> args = handle.getArgumentNames();
for (auto arg = args.begin(); arg != args.end(); ++arg) {
if (appendArgument(SRV_CONFIG_MARKER_FILE, arg->c_str()) != 0) {
return (1);
}
}
return (0);
}
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -17,6 +17,10 @@
/// can determine whether the load/unload functions have been run and, if so, /// can determine whether the load/unload functions have been run and, if so,
/// in what order. /// in what order.
/// ///
/// The additional marker file is created for the dhcp4_srv_configured hook
/// point. It records the library number and the names of the parameters
/// provided to the callout.
///
/// This file is the common library file for the tests. It will not compile /// This file is the common library file for the tests. It will not compile
/// by itself - it is included into each callout library which specifies the /// by itself - it is included into each callout library which specifies the
/// missing constant LIBRARY_NUMBER before the inclusion. /// missing constant LIBRARY_NUMBER before the inclusion.
@ -53,6 +57,26 @@ appendDigit(const char* name) {
return (0); return (0);
} }
/// @brief Append argument name passed to the callout to a marker file.
///
/// @param file_name Name of the file to open.
/// @param parameter Parameter name.
///
/// @return 0 on success, non-zero on error.
int appendArgument(const char* file_name, const char* argument) {
// Open the file and check if successful.
std::fstream file(file_name, std::fstream::out | std::fstream::app);
if (!file.good()) {
return (1);
}
// Add the library number to it and close.
file << argument;
file.close();
return (0);
}
// Framework functions // Framework functions
int int
version() { version() {

View File

@ -406,6 +406,15 @@ Dhcp4Client::doRequest() {
} }
} }
void
Dhcp4Client::receiveResponse() {
context_.response_ = receiveOneMsg();
// If the server has responded, store the configuration received.
if (context_.response_) {
applyConfiguration();
}
}
void void
Dhcp4Client::includeClientId(const std::string& clientid) { Dhcp4Client::includeClientId(const std::string& clientid) {
if (clientid.empty()) { if (clientid.empty()) {
@ -519,7 +528,14 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
msg_copy->setLocalAddr(dest_addr_); msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface(iface_name_); msg_copy->setIface(iface_name_);
srv_->fakeReceive(msg_copy); srv_->fakeReceive(msg_copy);
srv_->run();
try {
// Invoke run_one instead of run, because we want to avoid triggering
// IO service.
srv_->run_one();
} catch (...) {
// Suppress errors, as the DHCPv4 server does.
}
} }
void void

View File

@ -206,6 +206,15 @@ public:
/// DHCPACK message is applied and can be obtained from the @c config_. /// DHCPACK message is applied and can be obtained from the @c config_.
void doRequest(); void doRequest();
/// @brief Receives a response from the server.
///
/// This method is useful to receive response from the server after
/// parking a packet. In this case, the packet is not received as a
/// result of initial exchange, e.g. @c doRequest. The test can call
/// this method to complete the transaction when it expects that the
/// packet has been unparked.
void receiveResponse();
/// @brief Generates a hardware address used by the client. /// @brief Generates a hardware address used by the client.
/// ///
/// It assigns random values to the bytes of the hardware address. /// It assigns random values to the bytes of the hardware address.

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -17,6 +17,7 @@
#include <dhcp/pkt4.h> #include <dhcp/pkt4.h>
#include <dhcp/pkt_filter.h> #include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter_inet.h> #include <dhcp/pkt_filter_inet.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/subnet.h> #include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease.h> #include <dhcpsrv/lease.h>
#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/lease_mgr_factory.h>
@ -170,6 +171,31 @@ public:
virtual ~NakedDhcpv4Srv() { virtual ~NakedDhcpv4Srv() {
} }
/// @brief Runs processing DHCPREQUEST.
///
/// @param request a message received from client
/// @return DHCPACK or DHCPNAK message
Pkt4Ptr processRequest(Pkt4Ptr& request) {
AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
return (processRequest(request, context));
}
/// @brief Runs processing DHCPRELEASE.
///
/// @param release message received from client
void processRelease(Pkt4Ptr& release) {
AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
processRelease(release, context);
}
/// @brief Runs processing DHCPDECLINE
///
/// @param decline message received from client
void processDecline(Pkt4Ptr& decline) {
AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
processDecline(decline, context);
}
/// @brief Dummy server identifier option used by various tests. /// @brief Dummy server identifier option used by various tests.
OptionPtr server_id_; OptionPtr server_id_;

View File

@ -1,4 +1,4 @@
// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,7 +8,9 @@
#include <dhcp4/tests/dhcp4_test_utils.h> #include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp4/tests/dhcp4_client.h> #include <dhcp4/tests/dhcp4_client.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/json_config_parser.h> #include <dhcp4/json_config_parser.h>
#include <asiolink/io_service.h>
#include <cc/command_interpreter.h> #include <cc/command_interpreter.h>
#include <config/command_mgr.h> #include <config/command_mgr.h>
#include <hooks/server_hooks.h> #include <hooks/server_hooks.h>
@ -18,6 +20,7 @@
#include <dhcp/tests/iface_mgr_test_config.h> #include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp/option.h> #include <dhcp/option.h>
#include <asiolink/io_address.h> #include <asiolink/io_address.h>
#include <dhcp4/tests/dhcp4_client.h>
#include <dhcp4/tests/marker_file.h> #include <dhcp4/tests/marker_file.h>
#include <dhcp4/tests/test_libraries.h> #include <dhcp4/tests/test_libraries.h>
@ -39,6 +42,7 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
int hook_index_buffer4_receive = -1; int hook_index_buffer4_receive = -1;
int hook_index_pkt4_receive = -1; int hook_index_pkt4_receive = -1;
int hook_index_select_subnet = -1; int hook_index_select_subnet = -1;
int hook_index_leases4_committed = -1;
int hook_index_lease4_release = -1; int hook_index_lease4_release = -1;
int hook_index_pkt4_send = -1; int hook_index_pkt4_send = -1;
int hook_index_buffer4_send = -1; int hook_index_buffer4_send = -1;
@ -51,6 +55,8 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
.getIndex("pkt4_receive")); .getIndex("pkt4_receive"));
EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks() EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
.getIndex("subnet4_select")); .getIndex("subnet4_select"));
EXPECT_NO_THROW(hook_index_leases4_committed = ServerHooks::getServerHooks()
.getIndex("leases4_committed"));
EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks() EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks()
.getIndex("lease4_release")); .getIndex("lease4_release"));
EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks() EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks()
@ -63,6 +69,7 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
EXPECT_TRUE(hook_index_buffer4_receive > 0); EXPECT_TRUE(hook_index_buffer4_receive > 0);
EXPECT_TRUE(hook_index_pkt4_receive > 0); EXPECT_TRUE(hook_index_pkt4_receive > 0);
EXPECT_TRUE(hook_index_select_subnet > 0); EXPECT_TRUE(hook_index_select_subnet > 0);
EXPECT_TRUE(hook_index_leases4_committed > 0);
EXPECT_TRUE(hook_index_lease4_release > 0); EXPECT_TRUE(hook_index_lease4_release > 0);
EXPECT_TRUE(hook_index_pkt4_send > 0); EXPECT_TRUE(hook_index_pkt4_send > 0);
EXPECT_TRUE(hook_index_buffer4_send > 0); EXPECT_TRUE(hook_index_buffer4_send > 0);
@ -102,11 +109,14 @@ public:
// clear static buffers // clear static buffers
resetCalloutBuffers(); resetCalloutBuffers();
io_service_ = boost::make_shared<IOService>();
} }
/// @brief destructor (deletes Dhcpv4Srv) /// @brief destructor (deletes Dhcpv4Srv)
virtual ~HooksDhcpv4SrvTest() { virtual ~HooksDhcpv4SrvTest() {
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("dhcp4_srv_configured");
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive");
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive");
@ -124,7 +134,7 @@ public:
/// @brief creates an option with specified option code /// @brief creates an option with specified option code
/// ///
/// This method is static, because it is used from callouts /// This method is static, because it is used from callouts
/// that do not have a pointer to HooksDhcpv4SSrvTest object /// that do not have a pointer to HooksDhcpv4SrvTest object
/// ///
/// @param option_code code of option to be created /// @param option_code code of option to be created
/// ///
@ -225,7 +235,7 @@ public:
// If there is at least one option with data // If there is at least one option with data
if (pkt->data_.size() >= Pkt4::DHCPV4_PKT_HDR_LEN) { if (pkt->data_.size() >= Pkt4::DHCPV4_PKT_HDR_LEN) {
// Offset of the first byte of the CHWADDR field. Let's the first // Offset of the first byte of the CHADDR field. Let's the first
// byte to some new value that we could later check // byte to some new value that we could later check
pkt->data_[28] = 0xff; pkt->data_[28] = 0xff;
} }
@ -621,6 +631,76 @@ public:
return (lease4_decline_callout(callout_handle)); return (lease4_decline_callout(callout_handle));
} }
/// Test callback that stores values passed to leases4_committed.
static int
leases4_committed_callout(CalloutHandle& callout_handle) {
callback_name_ = string("leases4_committed");
callout_handle.getArgument("query4", callback_qry_pkt4_);
Lease4CollectionPtr leases4;
callout_handle.getArgument("leases4", leases4);
if (leases4->size() > 0) {
callback_lease4_ = leases4->at(0);
}
Lease4CollectionPtr deleted_leases4;
callout_handle.getArgument("deleted_leases4", deleted_leases4);
if (deleted_leases4->size() > 0) {
callback_deleted_lease4_ = deleted_leases4->at(0);
}
callback_argument_names_ = callout_handle.getArgumentNames();
sort(callback_argument_names_.begin(), callback_argument_names_.end());
if (callback_qry_pkt4_) {
callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
}
return (0);
}
static void
leases4_committed_unpark(ParkingLotHandlePtr parking_lot, Pkt4Ptr query) {
parking_lot->unpark(query);
}
/// Test callback which asks the server to park the packet.
static int
leases4_committed_park_callout(CalloutHandle& callout_handle) {
callback_name_ = string("leases4_committed");
callout_handle.getArgument("query4", callback_qry_pkt4_);
io_service_->post(boost::bind(&HooksDhcpv4SrvTest::leases4_committed_unpark,
callout_handle.getParkingLotHandlePtr(),
callback_qry_pkt4_));
callout_handle.getParkingLotHandlePtr()->reference(callback_qry_pkt4_);
callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
Lease4CollectionPtr leases4;
callout_handle.getArgument("leases4", leases4);
if (leases4->size() > 0) {
callback_lease4_ = leases4->at(0);
}
Lease4CollectionPtr deleted_leases4;
callout_handle.getArgument("deleted_leases4", deleted_leases4);
if (deleted_leases4->size() > 0) {
callback_deleted_lease4_ = deleted_leases4->at(0);
}
callback_argument_names_ = callout_handle.getArgumentNames();
sort(callback_argument_names_.begin(), callback_argument_names_.end());
if (callback_qry_pkt4_) {
callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
}
return (0);
}
/// @brief Test host4_identifier callout by setting identifier to "foo" /// @brief Test host4_identifier callout by setting identifier to "foo"
/// ///
/// @param callout_handle handle passed by the hooks framework /// @param callout_handle handle passed by the hooks framework
@ -683,6 +763,7 @@ public:
callback_qry_pkt4_.reset(); callback_qry_pkt4_.reset();
callback_qry_pkt4_.reset(); callback_qry_pkt4_.reset();
callback_lease4_.reset(); callback_lease4_.reset();
callback_deleted_lease4_.reset();
callback_hwaddr_.reset(); callback_hwaddr_.reset();
callback_clientid_.reset(); callback_clientid_.reset();
callback_subnet4_.reset(); callback_subnet4_.reset();
@ -695,6 +776,9 @@ public:
/// pointer to Dhcpv4Srv that is used in tests /// pointer to Dhcpv4Srv that is used in tests
NakedDhcpv4Srv* srv_; NakedDhcpv4Srv* srv_;
/// Pointer to the IO service used in the tests.
static IOServicePtr io_service_;
// The following fields are used in testing pkt4_receive_callout // The following fields are used in testing pkt4_receive_callout
/// String name of the received callout /// String name of the received callout
@ -706,9 +790,12 @@ public:
/// Server/response Pkt4 structure returned in the callout /// Server/response Pkt4 structure returned in the callout
static Pkt4Ptr callback_resp_pkt4_; static Pkt4Ptr callback_resp_pkt4_;
/// Lease4 structure returned in the callout /// Lease4 structure returned in the leases4_committed callout
static Lease4Ptr callback_lease4_; static Lease4Ptr callback_lease4_;
/// Lease4 structure returned in the leases4_committed callout
static Lease4Ptr callback_deleted_lease4_;
/// Hardware address returned in the callout /// Hardware address returned in the callout
static HWAddrPtr callback_hwaddr_; static HWAddrPtr callback_hwaddr_;
@ -736,6 +823,7 @@ public:
// The following fields are used in testing pkt4_receive_callout. // The following fields are used in testing pkt4_receive_callout.
// See fields description in the class for details // See fields description in the class for details
IOServicePtr HooksDhcpv4SrvTest::io_service_;
string HooksDhcpv4SrvTest::callback_name_; string HooksDhcpv4SrvTest::callback_name_;
Pkt4Ptr HooksDhcpv4SrvTest::callback_qry_pkt4_; Pkt4Ptr HooksDhcpv4SrvTest::callback_qry_pkt4_;
Pkt4Ptr HooksDhcpv4SrvTest::callback_resp_pkt4_; Pkt4Ptr HooksDhcpv4SrvTest::callback_resp_pkt4_;
@ -743,6 +831,7 @@ Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_; HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_;
ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_; ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_;
Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_; Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_;
Lease4Ptr HooksDhcpv4SrvTest::callback_deleted_lease4_;
const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_; const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
vector<string> HooksDhcpv4SrvTest::callback_argument_names_; vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
bool HooksDhcpv4SrvTest::callback_qry_options_copy_; bool HooksDhcpv4SrvTest::callback_qry_options_copy_;
@ -775,10 +864,12 @@ 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));
static_cast<void>(remove(SRV_CONFIG_MARKER_FILE));
CfgMgr::instance().clear(); CfgMgr::instance().clear();
} }
}; };
// Checks if callouts installed on pkt4_receive are indeed called and the // Checks if callouts installed on pkt4_receive are indeed called and the
// all necessary parameters are passed. // all necessary parameters are passed.
// //
@ -1526,29 +1617,46 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
EXPECT_TRUE((*subnets)[1]->inPool(Lease::TYPE_V4, addr)); EXPECT_TRUE((*subnets)[1]->inPool(Lease::TYPE_V4, addr));
} }
// Checks that subnet4_select is able to drop the packet. // This test verifies that the leases4_committed hook point is not triggered
TEST_F(HooksDhcpv4SrvTest, subnet4SelectDrop) { // for the DHCPDISCOVER.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedDiscover) {
IfaceMgrTestConfig test_config(true); IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4(); IfaceMgr::instance().openSockets4();
// Install subnet4_select_drop callout ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout));
"subnet4_select", subnet4_select_drop_callout));
// Let's create a simple DISCOVER
Pkt4Ptr discover = generateSimpleDiscover();
// Simulate that we have received that traffic Dhcp4Client client(Dhcp4Client::SELECTING);
srv_->fakeReceive(discover); client.setIfaceName("eth1");
ASSERT_NO_THROW(client.doDiscover());
// Server will now process to run its normal loop, but instead of calling // Make sure that we received a response
// IfaceMgr::receive4(), it will read all packets from the list set by ASSERT_TRUE(client.getContext().response_);
// fakeReceive()
// In particular, it should call registered subnet4_select callback.
srv_->run();
// Check that there is no packet sent // Make sure that the callout wasn't called.
EXPECT_EQ(0, srv_->fake_sent_.size()); EXPECT_TRUE(callback_name_.empty());
}
// This test verifies that the leases4_committed hook point is not triggered
// for the DHCPINFORM.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedInform) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_callout));
Dhcp4Client client(Dhcp4Client::SELECTING);
client.useRelay();
ASSERT_NO_THROW(client.doInform());
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
// Make sure that the callout wasn't called.
EXPECT_TRUE(callback_name_.empty());
} }
// This test verifies that incoming (positive) REQUEST/Renewing can be handled // This test verifies that incoming (positive) REQUEST/Renewing can be handled
@ -1719,6 +1827,189 @@ TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr)); EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
} }
// This test verifies that the callout installed on the leases4_committed hook
// point is executed as a result of DHCPREQUEST message sent to allocate new
// lease or renew an existing lease.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedRequest) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_callout));
Dhcp4Client client(Dhcp4Client::SELECTING);
client.setIfaceName("eth1");
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// Check if all expected parameters were really received
vector<string> expected_argument_names;
expected_argument_names.push_back("query4");
expected_argument_names.push_back("deleted_leases4");
expected_argument_names.push_back("leases4");
sort(expected_argument_names.begin(), expected_argument_names.end());
EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
// Newly allocated lease should be returned.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
// Deleted lease must not be present, because it is a new allocation.
EXPECT_FALSE(callback_deleted_lease4_);
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
resetCalloutBuffers();
// Renew the lease and make sure that the callout has been executed.
client.setState(Dhcp4Client::RENEWING);
ASSERT_NO_THROW(client.doRequest());
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// Renewed lease should be returned.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
// Deleted lease must not be present, because it is a new allocation.
EXPECT_FALSE(callback_deleted_lease4_);
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
resetCalloutBuffers();
// Let's try to renew again but force the client to request a different
// address.
client.ciaddr_ = IOAddress("192.0.2.101");
ASSERT_NO_THROW(client.doRequest());
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// New lease should be returned.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.101", callback_lease4_->addr_.toText());
// The old lease should have been deleted.
ASSERT_TRUE(callback_deleted_lease4_);
EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->addr_.toText());
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
resetCalloutBuffers();
// Now request an address that can't be allocated. The client should receive
// the DHCPNAK.
client.ciaddr_ = IOAddress("10.0.0.1");
ASSERT_NO_THROW(client.doRequest());
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
EXPECT_FALSE(callback_lease4_);
EXPECT_FALSE(callback_deleted_lease4_);
}
// This test verifies that it is possible to park a packet as a result of
// the leases4_committed callouts.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedParkRequests) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
// This callout uses provided IO service object to post a function
// that unparks the packet. The packet is parked and can be unparked
// by simply calling IOService::poll.
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_park_callout));
// Create first client and perform DORA.
Dhcp4Client client1(Dhcp4Client::SELECTING);
client1.setIfaceName("eth1");
ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
// We should be offered an address but the DHCPACK should not arrive
// at this point, because the packet is parked.
ASSERT_FALSE(client1.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// Check if all expected parameters were really received
vector<string> expected_argument_names;
expected_argument_names.push_back("query4");
expected_argument_names.push_back("deleted_leases4");
expected_argument_names.push_back("leases4");
sort(expected_argument_names.begin(), expected_argument_names.end());
EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
// Newly allocated lease should be passed to the callout.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
// Deleted lease must not be present, because it is a new allocation.
EXPECT_FALSE(callback_deleted_lease4_);
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
// Reset all indicators because we'll be now creating a second client.
resetCalloutBuffers();
// Create the second client to test that it may communicate with the
// server while the previous packet is parked.
Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
client2.setIfaceName("eth1");
ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.101"))));
// The DHCPOFFER should have been returned but not DHCPACK, as this
// packet got parked too.
ASSERT_FALSE(client2.getContext().response_);
// Check that the callback called is indeed the one we installed.
EXPECT_EQ("leases4_committed", callback_name_);
// There should be now two actions scheduled on our IO service
// by the invoked callouts. They unpark both DHCPACK messages.
ASSERT_NO_THROW(io_service_->poll());
// Receive and check the first response.
ASSERT_NO_THROW(client1.receiveResponse());
ASSERT_TRUE(client1.getContext().response_);
Pkt4Ptr rsp = client1.getContext().response_;
EXPECT_EQ(DHCPACK, rsp->getType());
EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText());
// Receive and check the second response.
ASSERT_NO_THROW(client2.receiveResponse());
ASSERT_TRUE(client2.getContext().response_);
rsp = client2.getContext().response_;
EXPECT_EQ(DHCPACK, rsp->getType());
EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText());
}
// This test verifies that valid RELEASE triggers lease4_release callouts // This test verifies that valid RELEASE triggers lease4_release callouts
TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) { TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
IfaceMgrTestConfig test_config(true); IfaceMgrTestConfig test_config(true);
@ -1873,6 +2164,50 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
EXPECT_EQ(leases.size(), 1); EXPECT_EQ(leases.size(), 1);
} }
// This test verifies that the leases4_committed callout is executed
// with deleted leases as argument when DHCPRELEASE is processed.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedRelease) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_callout));
Dhcp4Client client(Dhcp4Client::SELECTING);
client.setIfaceName("eth1");
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
resetCalloutBuffers();
ASSERT_NO_THROW(client.doRelease());
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// Check if all expected parameters were really received
vector<string> expected_argument_names;
expected_argument_names.push_back("query4");
expected_argument_names.push_back("deleted_leases4");
expected_argument_names.push_back("leases4");
sort(expected_argument_names.begin(), expected_argument_names.end());
EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
// No new allocations.
EXPECT_FALSE(callback_lease4_);
// Released lease should be returned.
ASSERT_TRUE(callback_deleted_lease4_);
EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->addr_.toText());
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
}
// This test verifies that drop flag returned by a callout installed on the // This test verifies that drop flag returned by a callout installed on the
// lease4_release hook point will keep the lease // lease4_release hook point will keep the lease
TEST_F(HooksDhcpv4SrvTest, lease4ReleaseDrop) { TEST_F(HooksDhcpv4SrvTest, lease4ReleaseDrop) {
@ -2083,6 +2418,50 @@ TEST_F(HooksDhcpv4SrvTest, HooksDeclineDrop) {
EXPECT_EQ(addr, callback_lease4_->addr_); EXPECT_EQ(addr, callback_lease4_->addr_);
} }
// This test verifies that the leases4_committed callout is executed
// with declined leases as argument when DHCPDECLINE is processed.
TEST_F(HooksDhcpv4SrvTest, leases4CommittedDecline) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_callout));
Dhcp4Client client(Dhcp4Client::SELECTING);
client.useRelay();
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
// Make sure that we received a response
ASSERT_TRUE(client.getContext().response_);
resetCalloutBuffers();
ASSERT_NO_THROW(client.doDecline());
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
// Check if all expected parameters were really received
vector<string> expected_argument_names;
expected_argument_names.push_back("query4");
expected_argument_names.push_back("deleted_leases4");
expected_argument_names.push_back("leases4");
sort(expected_argument_names.begin(), expected_argument_names.end());
EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
// No new allocations.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
EXPECT_EQ(Lease::STATE_DECLINED, callback_lease4_->state_);
// Released lease should be returned.
EXPECT_FALSE(callback_deleted_lease4_);
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
}
// Checks if callout installed on host4_identifier can generate an // Checks if callout installed on host4_identifier can generate an
// identifier and whether that identifier is actually used. // identifier and whether that identifier is actually used.
@ -2258,3 +2637,66 @@ TEST_F(LoadUnloadDhcpv4SrvTest, unloadLibraries) {
EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21")); EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12")); EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
} }
// Checks if callouts installed on the dhcp4_srv_configured ared indeed called
// and all the necessary parameters are passed.
TEST_F(LoadUnloadDhcpv4SrvTest, Dhcpv4SrvConfigured) {
boost::shared_ptr<ControlledDhcpv4Srv> srv(new ControlledDhcpv4Srv(0));
// Ensure no marker files to start with.
ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
ASSERT_FALSE(checkMarkerFileExists(SRV_CONFIG_MARKER_FILE));
// Minimal valid configuration for the server. It includes the
// section which loads the callout library #3, which implements
// dhcp4_srv_configured callout.
std::string config_str =
"{"
" \"interfaces-config\": {"
" \"interfaces\": [ ]"
" },"
" \"rebind-timer\": 2000,"
" \"renew-timer\": 1000,"
" \"subnet4\": [ ],"
" \"valid-lifetime\": 4000,"
" \"lease-database\": {"
" \"type\": \"memfile\","
" \"persist\": false"
" },"
" \"hooks-libraries\": ["
" {"
" \"library\": \"" + std::string(CALLOUT_LIBRARY_3) + "\""
" }"
" ]"
"}";
ConstElementPtr config = Element::fromJSON(config_str);
// Configure the server.
ConstElementPtr answer;
ASSERT_NO_THROW(answer = srv->processConfig(config));
// Make sure there were no errors.
int status_code;
parseAnswer(status_code, answer);
ASSERT_EQ(0, status_code);
// The hook library should have been loaded.
EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3"));
EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
// The dhcp4_srv_configured should have been invoked and the provided
// parameters should be recorded.
EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE,
"3io_contextjson_configserver_config"));
// Destroy the server, instance which should unload the libraries.
srv.reset();
// The server was destroyed, so the unload() function should now
// include the library number in its marker file.
EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3"));
EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "3"));
EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE,
"3io_contextjson_configserver_config"));
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -14,6 +14,7 @@
namespace { namespace {
const char* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt"; const char* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt"; const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
const char* const SRV_CONFIG_MARKER_FILE = "@abs_builddir@/srv_config_marker_file.txt";
} }
namespace isc { namespace isc {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -21,6 +21,7 @@ namespace {
// operation. // operation.
const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1.so"; const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1.so";
const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2.so"; const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2.so";
const char* const CALLOUT_LIBRARY_3 = "@abs_builddir@/.libs/libco3.so";
// Name of a library which is not present. // Name of a library which is not present.
const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so"; const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";

View File

@ -2734,8 +2734,8 @@ AllocEngine::ClientContext4::ClientContext4()
requested_address_(IOAddress::IPV4_ZERO_ADDRESS()), requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
fwd_dns_update_(false), rev_dns_update_(false), fwd_dns_update_(false), rev_dns_update_(false),
hostname_(""), callout_handle_(), fake_allocation_(false), hostname_(""), callout_handle_(), fake_allocation_(false),
old_lease_(), hosts_(), conflicting_lease_(), query_(), old_lease_(), new_lease_(), hosts_(), conflicting_lease_(),
host_identifiers_() { query_(), host_identifiers_() {
} }
AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet, AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
@ -2750,8 +2750,8 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
requested_address_(requested_addr), requested_address_(requested_addr),
fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update), fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
hostname_(hostname), callout_handle_(), hostname_(hostname), callout_handle_(),
fake_allocation_(fake_allocation), old_lease_(), hosts_(), fake_allocation_(fake_allocation), old_lease_(), new_lease_(),
host_identifiers_() { hosts_(), host_identifiers_() {
// Initialize host identifiers. // Initialize host identifiers.
if (hwaddr) { if (hwaddr) {
@ -2776,8 +2776,7 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
// be later set to non NULL value if existing lease is found in the // be later set to non NULL value if existing lease is found in the
// database. // database.
ctx.old_lease_.reset(); ctx.old_lease_.reset();
ctx.new_lease_.reset();
Lease4Ptr new_lease;
// Before we start allocation process, we need to make sure that the // Before we start allocation process, we need to make sure that the
// selected subnet is allowed for this client. If not, we'll try to // selected subnet is allowed for this client. If not, we'll try to
@ -2798,7 +2797,12 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
isc_throw(BadValue, "HWAddr must be defined"); isc_throw(BadValue, "HWAddr must be defined");
} }
new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx); if (ctx.fake_allocation_) {
return (discoverLease4(ctx));
} else {
ctx.new_lease_ = requestLease4(ctx);
}
} catch (const isc::Exception& e) { } catch (const isc::Exception& e) {
// Some other error, return an empty lease. // Some other error, return an empty lease.
@ -2807,7 +2811,7 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
.arg(e.what()); .arg(e.what());
} }
return (new_lease); return (ctx.new_lease_);
} }
void void
@ -2992,6 +2996,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
if (!ctx.old_lease_ && client_lease) { if (!ctx.old_lease_ && client_lease) {
ctx.old_lease_ = client_lease; ctx.old_lease_ = client_lease;
} }
return (new_lease); return (new_lease);
} }

View File

@ -1104,6 +1104,9 @@ public:
/// @brief A pointer to an old lease that the client had before update. /// @brief A pointer to an old lease that the client had before update.
Lease4Ptr old_lease_; Lease4Ptr old_lease_;
/// @brief A pointer to a newly allocated lease.
Lease4Ptr new_lease_;
/// @brief Holds a map of hosts belonging to the client within different /// @brief Holds a map of hosts belonging to the client within different
/// subnets. /// subnets.
/// ///

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -413,6 +413,9 @@ typedef boost::shared_ptr<Lease4> Lease4Ptr;
/// @brief A collection of IPv4 leases. /// @brief A collection of IPv4 leases.
typedef std::vector<Lease4Ptr> Lease4Collection; typedef std::vector<Lease4Ptr> Lease4Collection;
/// @brief A shared pointer to the collection of IPv4 leases.
typedef boost::shared_ptr<Lease4Collection> Lease4CollectionPtr;
/// @brief Structure that holds a lease for IPv6 address and/or prefix /// @brief Structure that holds a lease for IPv6 address and/or prefix
/// ///
/// For performance reasons it is a simple structure, not a class. If we chose /// For performance reasons it is a simple structure, not a class. If we chose
@ -550,6 +553,10 @@ typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
/// @brief A collection of IPv6 leases. /// @brief A collection of IPv6 leases.
typedef std::vector<Lease6Ptr> Lease6Collection; typedef std::vector<Lease6Ptr> Lease6Collection;
/// @brief A shared pointer to the collection of IPv6 leases.
typedef boost::shared_ptr<Lease6Collection> Lease6CollectionPtr;
/// @brief Stream output operator. /// @brief Stream output operator.
/// ///
/// Dumps the output of Lease::toText to the given stream. /// Dumps the output of Lease::toText to the given stream.

View File

@ -42,6 +42,7 @@ libkea_hooks_la_SOURCES += libinfo.cc libinfo.h
libkea_hooks_la_SOURCES += library_handle.cc library_handle.h libkea_hooks_la_SOURCES += library_handle.cc library_handle.h
libkea_hooks_la_SOURCES += library_manager.cc library_manager.h libkea_hooks_la_SOURCES += library_manager.cc library_manager.h
libkea_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h libkea_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h
libkea_hooks_la_SOURCES += parking_lots.h
libkea_hooks_la_SOURCES += pointer_converter.h libkea_hooks_la_SOURCES += pointer_converter.h
libkea_hooks_la_SOURCES += server_hooks.cc server_hooks.h libkea_hooks_la_SOURCES += server_hooks.cc server_hooks.h

View File

@ -74,6 +74,11 @@ CalloutHandle::getArgumentNames() const {
return (names); return (names);
} }
ParkingLotHandlePtr
CalloutHandle::getParkingLotHandlePtr() const {
return (boost::make_shared<ParkingLotHandle>(server_hooks_.getParkingLotPtr(manager_->getHookIndex())));
}
// Return the library handle allowing the callout to access the CalloutManager // Return the library handle allowing the callout to access the CalloutManager
// registration/deregistration functions. // registration/deregistration functions.

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,6 +9,7 @@
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <hooks/library_handle.h> #include <hooks/library_handle.h>
#include <hooks/parking_lots.h>
#include <boost/any.hpp> #include <boost/any.hpp>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
@ -91,7 +92,8 @@ public:
enum CalloutNextStep { enum CalloutNextStep {
NEXT_STEP_CONTINUE = 0, ///< continue normally NEXT_STEP_CONTINUE = 0, ///< continue normally
NEXT_STEP_SKIP = 1, ///< skip the next processing step NEXT_STEP_SKIP = 1, ///< skip the next processing step
NEXT_STEP_DROP = 2 ///< drop the packet NEXT_STEP_DROP = 2, ///< drop the packet
NEXT_STEP_PARK = 3 ///< park the packet
}; };
@ -223,6 +225,11 @@ public:
/// NEXT_STEP_DROP - tells the server to unconditionally drop the packet /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet
/// and do not process it further. /// and do not process it further.
/// ///
/// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will
/// wait in the queue for being unparked, e.g. as a result
/// of completion of the asynchronous performed by the
/// hooks library operation.
///
/// This variable is interrogated by the server to see if the remaining /// This variable is interrogated by the server to see if the remaining
/// callouts associated with the current hook should be bypassed. /// callouts associated with the current hook should be bypassed.
/// ///
@ -337,7 +344,13 @@ public:
/// @return Name of the current hook or the empty string if none. /// @return Name of the current hook or the empty string if none.
std::string getHookName() const; std::string getHookName() const;
/// @brief Returns pointer to the parking lot handle for this hook point.
///
/// @return pointer to the parking lot handle
ParkingLotHandlePtr getParkingLotHandlePtr() const;
private: private:
/// @brief Check index /// @brief Check index
/// ///
/// Gets the current library index, throwing an exception if it is not set /// Gets the current library index, throwing an exception if it is not set

View File

@ -130,6 +130,7 @@ HooksManager::unloadLibrariesInternal() {
// ease debugging. // ease debugging.
lm_collection_.reset(); lm_collection_.reset();
callout_manager_.reset(); callout_manager_.reset();
ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
} }
void HooksManager::unloadLibraries() { void HooksManager::unloadLibraries() {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -234,6 +234,82 @@ public:
/// @return A reference to the shared callout manager /// @return A reference to the shared callout manager
static boost::shared_ptr<CalloutManager>& getSharedCalloutManager(); static boost::shared_ptr<CalloutManager>& getSharedCalloutManager();
/// @brief Park an object (packet).
///
/// The typical use case for parking an object is when the server needs to
/// suspend processing of a packet to perform an asynchronous operation,
/// before the response is sent to a client. In this case, the object type
/// is a pointer to the processed packet. Therefore, further in this
/// description we're going to refer to the parked objects as "parked
/// packets". However, any other object can be parked if necessary.
///
/// The following is the typical flow when packets are parked. The callouts
/// responsible for performing an asynchronous operation signal this need
/// to the server by returning the status @c NEXT_STEP_PARK, which instructs
/// the server to call this function. This function stops processing the
/// packet and puts it in, so called, parking lot. In order to be able to
/// resume the packet processing when instructed by the hooks library, the
/// parked packet is associated with the callback which, when called, will
/// resume packet processing.
///
/// The hook library must increase a reference count on the parked object
/// by calling @c ParkingLotHandle::reference prior to returning the
/// @c NEXT_STEP_PARK status. This is important when multiple callouts
/// are installed on the same hook point and each of them schedules an
/// asynchronous operation. In this case, the packet must not be unparked
/// until all hook libraries call @c ParkingLotHandle::unpark to mark
/// that respective asynchronous operations are completed.
///
/// @tparam Type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object packet to be parked.
/// @param unpark_callback callback invoked when the packet is unparked.
template<typename T>
static void park(const std::string& hook_name, T parked_object,
std::function<void()> unpark_callback) {
getHooksManager().parkInternal(hook_name, parked_object, unpark_callback);
}
/// @brief Forces unparking the object (packet).
///
/// This method unparks the object regardless of the reference counting
/// value. This is used in the situations when the callouts fail to unpark
/// the packet for some reason.
///
/// @tparam T type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object to be unparked.
/// @return true if the specified object has been found, false otherwise.
template<typename T>
static bool unpark(const std::string& hook_name, T parked_object) {
return (getHooksManager().unparkInternal(hook_name, parked_object));
}
/// @brief Removes parked object without calling a callback.
///
/// @tparam T type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object to be removed.
/// @return true if the specified object has been found false otherwise.
template<typename T>
static bool drop(const std::string& hook_name, T parked_object) {
return (getHooksManager().dropInternal(hook_name, parked_object));
}
/// @brief Increases reference counter for the parked object.
///
/// Reference counter must be increased at least to 1 before the @c park()
/// method can be called.
///
/// @tparam Type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object for which reference counter should
/// be increased.
template<typename T>
static void reference(const std::string& hook_name, T parked_object) {
getHooksManager().referenceInternal(hook_name, parked_object);
}
private: private:
/// @brief Constructor /// @brief Constructor
@ -242,6 +318,55 @@ private:
/// through the getHooksManager() static method. /// through the getHooksManager() static method.
HooksManager(); HooksManager();
/// @brief Park an object (packet).
///
/// @tparam Type of the parked object.
/// @param hook_name Name of the hook point for which the packet is parked.
/// @param parked_object packet to be parked.
/// @param unpark_callback callback invoked when the packet is unparked.
template<typename T>
void parkInternal(const std::string& hook_name, T parked_object,
std::function<void()> unpark_callback) {
ServerHooks::getServerHooks().
getParkingLotPtr(hook_name)->park(parked_object, unpark_callback);
}
/// @brief Signals that the object (packet) should be unparked.
///
/// @tparam Type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object to be unparked.
/// @return true if the specified object has been found, false otherwise.
template<typename T>
bool unparkInternal(const std::string& hook_name, T parked_object) {
return (ServerHooks::getServerHooks().
getParkingLotPtr(hook_name)->unpark(parked_object, true));
}
/// @brief Removes parked object without calling a callback.
///
/// @tparam T type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object to be removed.
/// @return true if the specified object has been found false otherwise.
template<typename T>
static bool dropInternal(const std::string& hook_name, T parked_object) {
return (ServerHooks::getServerHooks().
getParkingLotPtr(hook_name)->drop(parked_object));
}
/// @brief Increases reference counter for the parked object.
///
/// @tparam Type of the parked object.
/// @param hook_name name of the hook point for which the packet is parked.
/// @param parked_object parked object for which reference counter should
/// be increased.
template<typename T>
void referenceInternal(const std::string& hook_name, T parked_object) {
ServerHooks::getServerHooks().
getParkingLotPtr(hook_name)->reference(parked_object);
}
//@{ //@{
/// The following methods correspond to similarly-named static methods, /// The following methods correspond to similarly-named static methods,
/// but actually do the work on the singleton instance of the HooksManager. /// but actually do the work on the singleton instance of the HooksManager.

View File

@ -0,0 +1,333 @@
// 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/.
#ifndef PARKING_LOTS_H
#define PARKING_LOTS_H
#include <exceptions/exceptions.h>
#include <boost/any.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <functional>
#include <list>
#include <map>
#include <iostream>
namespace isc {
namespace hooks {
/// @brief Parking lot for objects, e.g. packets, for a hook point.
///
/// Callouts may instruct the servers to "park" processed packets, i.e. suspend
/// their processing until explicitly unparked. This is useful in cases when
/// callouts need to perform asynchronous operations related to the packet
/// processing and the packet must not be further processed until the
/// asynchronous operations are completed. While the packet is parked, the
/// new packets can be processed, so the server remains responsive to the
/// new requests.
///
/// Parking lots are created per hook point, so the callouts installed on the
/// particular hook point only have access to the parking lots dedicated to
/// them.
///
/// The parking lot object supports 3 actions: "park", "unpark" and "reference".
/// In the typical case, the server parks the object and the callouts unpark and
/// reference the objects. Therefore, the @ref ParkingLot object is not passed
/// directly to the callouts. Instead, a ParkingLotHandle object is provided
/// to the callout, which only provides access to "unpark" and "reference"
/// operations.
///
/// Referencing an object is performed by the callouts before the
/// @c CalloutHandle::NEXT_STEP_PARK is returned to the server and before the
/// server parks the object. Trying to park unreferenced object will result
/// in error. Referencing (reference counting) as an important part of the
/// parking mechanism, which allows multiple callouts, installed on the same
/// hook point, to perform asynchronous operations and guarantees that the
/// object remains parked until all those asynchronous operations complete.
/// Each such callout must call @c unpark() when it desires the object to
/// be unparked, but the object will be unparked when all callouts call this
/// function, i.e. when all callouts signal completion of their respective
/// asynchronous operations.
///
/// The types of the parked objects provided as T parameter of respective
/// functions are most often shared pointers. One should not use references
/// to parked objects nor references to shared pointers to avoid premature
/// destruction of the parked objects.
class ParkingLot {
public:
/// @brief Parks an object.
///
/// @tparam Type of the parked object.
/// @param parked_object object to be parked, e.g. pointer to a packet.
/// @param unpark_callback callback function to be invoked when the object
/// is unparked.
/// @throw InvalidOperation if the @c reference() wasn't called prior to
/// parking the object.
template<typename T>
void park(T parked_object, std::function<void()> unpark_callback) {
auto it = find(parked_object);
if (it == parking_.end() || it->refcount_ <= 0) {
isc_throw(InvalidOperation, "unable to park an object because"
" reference count for this object hasn't been increased."
" Call ParkingLot::reference() first");
} else {
it->update(parked_object, unpark_callback);
}
}
/// @brief Increases reference counter for the parked object.
///
/// This method is called by the callouts to increase a reference count
/// on the object to be parked. It must be called before the object is
/// actually parked.
///
/// @tparam Type of the parked object.
/// @param parked_object object which will be parked.
template<typename T>
void reference(T parked_object) {
auto it = find(parked_object);
if (it == parking_.end()) {
ParkingInfo parking_info(parked_object);
parking_.push_back(parking_info);
} else {
++it->refcount_;
}
}
/// @brief Signals that the object should be unparked.
///
/// If the specified object is parked in this parking lot, the reference
/// count is decreased as a result of this method. If the reference count
/// is 0, the object is unparked and the callback is invoked. Typically, the
/// callback points to a function which resumes processing of a packet.
///
/// @tparam Type of the parked object.
/// @param parked_object parked object to be unparked.
/// @param force boolean value indicating if the reference counting should
/// be ignored and the object should be unparked immediately.
/// @return false if the object couldn't be unparked because there is
/// no such object, true otherwise.
template<typename T>
bool unpark(T parked_object, bool force = false) {
auto it = find(parked_object);
if (it != parking_.end()) {
if (force) {
it->refcount_ = 0;
} else {
--it->refcount_;
}
if (it->refcount_ <= 0) {
// Unpark the packet and invoke the callback.
std::function<void()> cb = it->unpark_callback_;
parking_.erase(it);
cb();
}
// Parked object found, so return true to indicate that the
// operation was successful. It doesn't necessarily mean
// that the object was unparked, but at least the reference
// count was decreased.
return (true);
}
// No such parked object.
return (false);
}
/// @brief Removes parked object without calling a callback.
///
/// @tparam Type of the parked object.
/// @param parked_object parked object to be removed.
/// @return false if the object couldn't be removed because there is
/// no such object, true otherwise.
template<typename T>
bool drop(T parked_object) {
auto it = find(parked_object);
if (it != parking_.end()) {
// Parked object found.
parking_.erase(it);
return (true);
}
// No such object.
return (false);
}
private:
/// @brief Holds information about parked object.
struct ParkingInfo {
boost::any parked_object_; ///< parked object
std::function<void()> unpark_callback_; ///< pointer to the callback
int refcount_; ///< current reference count
/// @brief Constructor.
///
/// @param parked_object object being parked.
/// @param callback pointer to the callback.
ParkingInfo(const boost::any& parked_object,
std::function<void()> callback = 0)
: parked_object_(parked_object), unpark_callback_(callback),
refcount_(1) {
}
/// @brief Update parking information.
///
/// @param parked_object parked object.
/// @param callback pointer to the callback.
void update(const boost::any& parked_object,
std::function<void()> callback) {
parked_object_ = parked_object;
unpark_callback_ = callback;
}
};
/// @brief Type of list of parked objects.
typedef std::list<ParkingInfo> ParkingInfoList;
/// @brief Type of the iterator in the list of parked objects.
typedef ParkingInfoList::iterator ParkingInfoListIterator;
/// @brief Container holding parked objects for this parking lot.
ParkingInfoList parking_;
/// @brief Search for the information about the parked object.
///
/// @tparam T parked object type.
/// @param parked_object object for which the information should be found.
/// @return Iterator pointing to the parked object, or @c parking_.end() if
/// no such object found.
template<typename T>
ParkingInfoListIterator find(T parked_object) {
for (auto it = parking_.begin(); it != parking_.end(); ++it) {
if (boost::any_cast<T>(it->parked_object_) == parked_object) {
return (it);
}
}
return (parking_.end());
}
};
/// @brief Type of the pointer to the parking lot.
typedef boost::shared_ptr<ParkingLot> ParkingLotPtr;
/// @brief Provides a limited view to the @c ParkingLot.
///
/// The handle is provided to the callouts which can reference and unpark
/// parked objects. The callouts should not park objects, therefore this
/// operation is not available.
///
/// The types of the parked objects provided as T parameter of respective
/// functions are most often shared pointers. One should not use references
/// to parked objects nor references to shared pointers to avoid premature
/// destruction of the parked objects.
class ParkingLotHandle {
public:
/// @brief Constructor.
///
/// @param parking_lot pointer to the parking lot for which the handle is
/// created.
ParkingLotHandle(const ParkingLotPtr& parking_lot)
: parking_lot_(parking_lot) {
}
/// @brief Increases reference counter for the parked object.
///
/// This method is called by the callouts to increase a reference count
/// on the object to be parked. It must be called before the object is
/// actually parked.
///
/// @tparam Type of the parked object.
/// @param parked_object object which will be parked.
template<typename T>
void reference(T parked_object) {
parking_lot_->reference(parked_object);
}
/// @brief Signals that the object should be unparked.
///
/// If the specified object is parked in this parking lot, the reference
/// count is decreased as a result of this method. If the reference count
/// is 0, the object is unparked and the callback is invoked. Typically, the
/// callback points to a function which resumes processing of a packet.
///
/// @tparam Type of the parked object.
/// @param parked_object parked object to be unparked.
/// @return false if the object couldn't be unparked because there is
/// no such object, true otherwise.
template<typename T>
bool unpark(T parked_object) {
return (parking_lot_->unpark(parked_object));
}
/// @brief Removes parked object without calling a callback.
///
/// It ignores any reference counts on the parked object.
///
/// @tparam Type of the parked object.
/// @param parked_object parked object to be removed.
/// @return false if the object couldn't be removed because there is
/// no such object, true otherwise.
template<typename T>
bool drop(T parked_object) {
return (parking_lot_->drop(parked_object));
}
private:
/// @brief Parking lot to which this handle points.
ParkingLotPtr parking_lot_;
};
/// @brief Pointer to the parking lot handle.
typedef boost::shared_ptr<ParkingLotHandle> ParkingLotHandlePtr;
/// @brief Collection of parking lots for various hook points.
class ParkingLots {
public:
/// @brief Removes all parked objects.
///
/// It doesn't invoke callbacks associated with the removed objects.
void clear() {
parking_lots_.clear();
}
/// @brief Returns pointer to the parking lot for a hook points.
///
/// If the parking lot for the specified hook point doesn't exist, it is
/// created.
///
/// @param hook_index index of the hook point with which the parking
/// lot is associated.
/// @return Pointer to the parking lot.
ParkingLotPtr getParkingLotPtr(const int hook_index) {
if (parking_lots_.count(hook_index) == 0) {
parking_lots_[hook_index] = boost::make_shared<ParkingLot>();
}
return (parking_lots_[hook_index]);
}
private:
/// @brief Container holding parking lots for various hook points.
std::map<int, ParkingLotPtr> parking_lots_;
};
/// @brief Type of the pointer to the parking lots.
typedef boost::shared_ptr<ParkingLots> ParkingLotsPtr;
} // end of namespace hooks
} // end of namespace isc
#endif

View File

@ -84,6 +84,7 @@ ServerHooks::initialize() {
// Clear out the name->index and index->name maps. // Clear out the name->index and index->name maps.
hooks_.clear(); hooks_.clear();
inverse_hooks_.clear(); inverse_hooks_.clear();
parking_lots_.reset(new ParkingLots());
// Register the pre-defined hooks. // Register the pre-defined hooks.
int create = registerHook("context_create"); int create = registerHook("context_create");
@ -175,6 +176,21 @@ ServerHooks::getServerHooksPtr() {
return (hooks); return (hooks);
} }
ParkingLotsPtr
ServerHooks::getParkingLotsPtr() const {
return (parking_lots_);
}
ParkingLotPtr
ServerHooks::getParkingLotPtr(const int hook_index) {
return (parking_lots_->getParkingLotPtr(hook_index));
}
ParkingLotPtr
ServerHooks::getParkingLotPtr(const std::string& hook_name) {
return (parking_lots_->getParkingLotPtr(getServerHooks().getIndex(hook_name)));
}
std::string std::string
ServerHooks::commandToHookName(const std::string& command_name) { ServerHooks::commandToHookName(const std::string& command_name) {
// Prefix the command name with a dollar sign. // Prefix the command name with a dollar sign.

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,6 +8,7 @@
#define SERVER_HOOKS_H #define SERVER_HOOKS_H
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <hooks/parking_lots.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
@ -152,6 +153,25 @@ public:
/// @return Pointer to the global ServerHooks object. /// @return Pointer to the global ServerHooks object.
static ServerHooksPtr getServerHooksPtr(); static ServerHooksPtr getServerHooksPtr();
/// @brief Returns pointer to all parking lots.
///
/// @return pointer to all parking lots.
ParkingLotsPtr getParkingLotsPtr() const;
/// @brief Returns pointer to the ParkingLot for the specified hook index.
///
/// @param hook_index index of the hook point for which the parking lot
/// should be returned.
/// @return Pointer to the ParkingLot object.
ParkingLotPtr getParkingLotPtr(const int hook_index);
/// @brief Returns pointer to the ParkingLot for the specified hook name.
///
/// @param hook_name name of the hook point for which the parking lot
/// should be returned.
/// @return Pointer to the ParkingLot object.
ParkingLotPtr getParkingLotPtr(const std::string& hook_name);
/// @brief Generates hook point name for the given control command name. /// @brief Generates hook point name for the given control command name.
/// ///
/// This function is called to generate the name of the hook point /// This function is called to generate the name of the hook point
@ -216,6 +236,8 @@ private:
/// simpler than using a multi-indexed container.) /// simpler than using a multi-indexed container.)
HookCollection hooks_; ///< Hook name/index collection HookCollection hooks_; ///< Hook name/index collection
InverseHookCollection inverse_hooks_; ///< Hook index/name collection InverseHookCollection inverse_hooks_; ///< Hook index/name collection
ParkingLotsPtr parking_lots_;
}; };
} // namespace util } // namespace util

View File

@ -40,7 +40,7 @@ if HAVE_GTEST
# ignored for unit tests built here. # ignored for unit tests built here.
noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \ noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \
liblecl.la libucl.la libfcl.la libpcl.la liblecl.la libucl.la libfcl.la libpcl.la libacl.la
# -rpath /nowhere is a hack to trigger libtool to not create a # -rpath /nowhere is a hack to trigger libtool to not create a
# convenience archive, resulting in shared modules # convenience archive, resulting in shared modules
@ -102,6 +102,12 @@ libpcl_la_CPPFLAGS = $(AM_CPPFLAGS)
libpcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere libpcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
libpcl_la_LDFLAGS += $(top_builddir)/src/lib/util/libkea-util.la libpcl_la_LDFLAGS += $(top_builddir)/src/lib/util/libkea-util.la
# The async callout library - parks object for asynchronous task
libacl_la_SOURCES = async_callout_library.cc
libacl_la_CXXFLAGS = $(AM_CXXFLAGS)
libacl_la_CPPFLAGS = $(AM_CPPFLAGS)
libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
TESTS += run_unittests TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += callout_handle_unittest.cc run_unittests_SOURCES += callout_handle_unittest.cc
@ -111,6 +117,7 @@ run_unittests_SOURCES += handles_unittest.cc
run_unittests_SOURCES += hooks_manager_unittest.cc run_unittests_SOURCES += hooks_manager_unittest.cc
run_unittests_SOURCES += library_manager_collection_unittest.cc run_unittests_SOURCES += library_manager_collection_unittest.cc
run_unittests_SOURCES += library_manager_unittest.cc run_unittests_SOURCES += library_manager_unittest.cc
run_unittests_SOURCES += parking_lots_unittest.cc
run_unittests_SOURCES += server_hooks_unittest.cc run_unittests_SOURCES += server_hooks_unittest.cc
nodist_run_unittests_SOURCES = marker_file.h nodist_run_unittests_SOURCES = marker_file.h

View File

@ -0,0 +1,153 @@
// 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/.
/// @file
/// @brief Async callout library
///
/// This is source of a test library for testing a "parking" feature, i.e.
/// the callouts can schedule asynchronous operation and indicate that the
/// packet should be parked until the asynchronous operation completes and
/// the hooks library indicates that packet processing should be resumed.
#include <config.h>
#include <hooks/hooks.h>
#include <hooks/parking_lots.h>
#include <log/logger.h>
#include <log/macros.h>
#include <log/message_initializer.h>
#include <algorithm>
#include <functional>
#include <sstream>
#include <string>
#include <vector>
using namespace isc::hooks;
using namespace isc::log;
using namespace std;
namespace {
/// @brief Logger used by the library.
isc::log::Logger logger("acl");
/// @brief Log messages.
const char* log_messages[] = {
"ACL_LOAD_START", "async callout load %1",
"ACL_LOAD_END", "async callout load end",
"ACL_LOAD_END", "duplicate of async callout load end",
NULL
};
/// @brief Initializer for log messages.
const MessageInitializer message_initializer(log_messages);
/// @brief Simple callback which unparks parked object.
///
/// @param parking_lot parking lot where the object is parked.
/// @param parked_object parked object.
void unpark(ParkingLotHandlePtr parking_lot, const std::string& parked_object) {
parking_lot->unpark(parked_object);
}
} // end of anonymous namespace
extern "C" {
/// @brief Callout scheduling object parking and providing function to unpark
/// it.
///
/// This callout is crafted to test the following scenario. The callout returns
/// status "park" to indicate that the packet should be parked. The callout
/// performs asynchronous operation and indicates that the packet should be
/// unparked when this operation completes. Unparking the packet triggers a
/// function associated with the parked packet, e.g. a function which continues
/// processing of this packet.
///
/// This test callout "parks" a string object instead of a packet. It assumes
/// that there might be multiple callouts installed on this hook point, which
/// all trigger asynchronous operation. The object must be unparked when the
/// last asynchronous operation completes. Therefore, it calls the @c reference
/// function on the parking lot object to increase the reference count. The
/// object remains parked as long as the reference counter is greater than
/// 0.
///
/// The callout returns 1 or more pointers to the functions which should be
/// called by the unit tests to simulate completion of the asynchronous tasks.
/// When the test calls those functions, @c unpark function is called, which
/// decreases reference count on the parked object, or actually unparks the
/// object when the reference count reaches 0.
///
/// @param handle Reference to callout handle used to set/get arguments.
int
hookpt_one(CalloutHandle& handle) {
// Using a string as "parked" object.
std::string parked_object;
handle.getArgument("parked_object", parked_object);
// Retrieve the parking lot handle for this hook point. It allows for
// increasing a reference count on the parked object and also for
// scheduling packet unparking.
ParkingLotHandlePtr parking_lot = handle.getParkingLotHandlePtr();
// Increase the reference count to indicate that this callout needs the
// object to remain parked until the asynchronous operation completes.
// Otherwise, other callouts could potentially call unpark and cause the
// packet processing to continue before the asynchronous operation
// completes.
parking_lot->reference(parked_object);
// Create pointer to the function that the test should call to simulate
// completion of the asynchronous operation scheduled by this callout.
std::function<void()> unpark_trigger_func =
std::bind(unpark, parking_lot, parked_object);
// Every callout (if multiple callouts installed on this hook point) should
// return the function pointer under unique name. The base name is
// "unpark_trigger" and the callouts append consecutive numbers to this
// base name, e.g. "unpark_trigger1", "unpark_trigger2" etc.
std::string fun_name;
std::vector<std::string> args = handle.getArgumentNames();
unsigned i = 1;
do {
std::ostringstream candidate_name;
candidate_name << "unpark_trigger" << i;
if (std::find(args.begin(), args.end(), candidate_name.str()) ==
args.end()) {
fun_name = candidate_name.str();
} else {
++i;
}
} while (fun_name.empty());
handle.setArgument(fun_name, unpark_trigger_func);
handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
return (0);
}
// Framework functions.
int
version() {
return (KEA_HOOKS_VERSION);
}
// load() initializes the user library if the main image was statically linked.
int
load(isc::hooks::LibraryHandle&) {
#ifdef USE_STATIC_LINK
hooksStaticLinkInit();
#endif
LOG_INFO(logger, "ACL_LOAD_START").arg("argument");
LOG_INFO(logger, "ACL_LOAD_END");
return (0);
}
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -750,6 +750,7 @@ calloutPrintSkip(CalloutHandle& handle) {
static const std::string YES("Y"); static const std::string YES("Y");
static const std::string NO("N"); static const std::string NO("N");
static const std::string DROP("D"); static const std::string DROP("D");
static const std::string PARK("P");
switch (handle.getStatus()) { switch (handle.getStatus()) {
case CalloutHandle::NEXT_STEP_CONTINUE: case CalloutHandle::NEXT_STEP_CONTINUE:
@ -761,6 +762,9 @@ calloutPrintSkip(CalloutHandle& handle) {
case CalloutHandle::NEXT_STEP_DROP: case CalloutHandle::NEXT_STEP_DROP:
HandlesTest::common_string_ += DROP; // drop HandlesTest::common_string_ += DROP; // drop
break; break;
case CalloutHandle::NEXT_STEP_PARK:
HandlesTest::common_string_ += PARK; // park
break;
} }
return (0); return (0);
} }
@ -893,6 +897,11 @@ calloutSetArgumentDrop(CalloutHandle& handle) {
return (calloutSetArgumentCommon(handle, "D")); return (calloutSetArgumentCommon(handle, "D"));
} }
int
calloutSetArgumentPark(CalloutHandle& handle) {
return (calloutSetArgumentCommon(handle, "P"));
}
// ... and a callout to just copy the argument to the "common_string_" variable // ... and a callout to just copy the argument to the "common_string_" variable
// but otherwise not alter it. // but otherwise not alter it.
@ -920,7 +929,9 @@ TEST_F(HandlesTest, CheckModifiedArgument) {
getCalloutManager()->setLibraryIndex(2); getCalloutManager()->setLibraryIndex(2);
getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip); getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip);
getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue); getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue);
getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark);
getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip); getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip);
getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark);
// Create the argument with an initial empty string value. Then call the // Create the argument with an initial empty string value. Then call the
// sequence of callouts above. // sequence of callouts above.
@ -935,7 +946,7 @@ TEST_F(HandlesTest, CheckModifiedArgument) {
EXPECT_EQ(std::string("SCC" "SD"), common_string_); EXPECT_EQ(std::string("SCC" "SD"), common_string_);
callout_handle.getArgument(MODIFIED_ARG, modified_arg); callout_handle.getArgument(MODIFIED_ARG, modified_arg);
EXPECT_EQ(std::string("SCC" "SDDC" "SCS"), modified_arg); EXPECT_EQ(std::string("SCC" "SDDC" "SCPSP"), modified_arg);
} }
// Test that the CalloutHandle provides the name of the hook to which the // Test that the CalloutHandle provides the name of the hook to which the

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -755,5 +755,205 @@ TEST_F(HooksManagerTest, LibraryParameters) {
EXPECT_NO_THROW(HooksManager::unloadLibraries()); EXPECT_NO_THROW(HooksManager::unloadLibraries());
} }
// This test verifies that an object can be parked in two different
// callouts and that it is unparked when the last callout calls the
// unpark function.
TEST_F(HooksManagerTest, Parking) {
// Load the same library twice. Both installed callouts will trigger
// asynchronous operation.
HookLibsCollection library_names;
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
// Load the libraries.
EXPECT_TRUE(HooksManager::loadLibraries(library_names));
CalloutHandlePtr handle = HooksManager::createCalloutHandle();
// We could be parked any object. Typically it will be a pointer to the
// packet. In this case, however, it is simpler to just use a string.
std::string parked_object = "foo";
handle->setArgument("parked_object", parked_object);
// Call both installed callouts.
HooksManager::callCallouts(hookpt_one_index_, *handle);
// This boolean value will be set to true when the packet gets unparked.
bool unparked = false;
// The callouts instruct us to park the object. We associated the callback
// function with the parked object, which sets "unparked" flag to true. We
// can later test the value of this flag to verify when exactly the packet
// got unparked.
ASSERT_NO_THROW(
HooksManager::park<std::string>("hookpt_one", "foo",
[&unparked] {
unparked = true;
})
);
// We have two callouts which should have returned pointers to the
// functions which we can call to siumulate completion of asynchronous
// tasks.
std::function<void()> unpark_trigger_func1;
handle->getArgument("unpark_trigger1", unpark_trigger_func1);
// Call the first function. It should cause the hook library to call the
// "unpark" function. However, the object should not be unparked yet,
// because the other callout hasn't completed its scheduled asynchronous
// operation (keeps a reference on the parked object).
unpark_trigger_func1();
EXPECT_FALSE(unparked);
// Call the second function. This should decrease the reference count to
// 0 and the packet should be unparked.
std::function<void()> unpark_trigger_func2;
handle->getArgument("unpark_trigger2", unpark_trigger_func2);
unpark_trigger_func2();
EXPECT_TRUE(unparked);
// Try unloading the libraries.
EXPECT_NO_THROW(HooksManager::unloadLibraries());
}
// This test verifies that the server can also unpark the packet.
TEST_F(HooksManagerTest, ServerUnpark) {
// Load the same library twice. Both installed callouts will trigger
// asynchronous operation.
HookLibsCollection library_names;
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
// Load libraries.
EXPECT_TRUE(HooksManager::loadLibraries(library_names));
CalloutHandlePtr handle = HooksManager::createCalloutHandle();
// We could be parked any object. Typically it will be a pointer to the
// packet. In this case, however, it is simpler to just use a string.
std::string parked_object = "foo";
handle->setArgument("parked_object", parked_object);
// Call installed callout.
HooksManager::callCallouts(hookpt_one_index_, *handle);
// This boolean value will be set to true when the packet gets unparked.
bool unparked = false;
// It should be possible for the server to increase reference counter.
ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo"));
// The callouts instruct us to park the object. We associated the callback
// function with the parked object, which sets "unparked" flag to true. We
// can later test the value of this flag to verify when exactly the packet
// got unparked.
HooksManager::park<std::string>("hookpt_one", "foo",
[&unparked] {
unparked = true;
});
// Server can force unparking the object.
EXPECT_TRUE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
EXPECT_TRUE(unparked);
// Try unloading the libraries.
EXPECT_NO_THROW(HooksManager::unloadLibraries());
}
// This test verifies that the server can drop parked packet.
TEST_F(HooksManagerTest, ServerDropParked) {
// Load library.
HookLibsCollection library_names;
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
EXPECT_TRUE(HooksManager::loadLibraries(library_names));
CalloutHandlePtr handle = HooksManager::createCalloutHandle();
// We could be parked any object. Typically it will be a pointer to the
// packet. In this case, however, it is simpler to just use a string.
std::string parked_object = "foo";
handle->setArgument("parked_object", parked_object);
// Call installed callout.
HooksManager::callCallouts(hookpt_one_index_, *handle);
// This boolean value will be set to true when the packet gets unparked.
bool unparked = false;
// It should be possible for the server to increase reference counter.
ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo"));
// The callouts instruct us to park the object. We associated the callback
// function with the parked object, which sets "unparked" flag to true. We
// can later test the value of this flag to verify when exactly the packet
// got unparked.
HooksManager::park<std::string>("hookpt_one", "foo",
[&unparked] {
unparked = true;
});
// Drop the parked packet. The callback should not be called.
EXPECT_TRUE(HooksManager::drop<std::string>("hookpt_one", "foo"));
EXPECT_FALSE(unparked);
// An attempt to unpark the packet should return false, as this packet
// is not parked anymore.
EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
// Try unloading the libraries.
EXPECT_NO_THROW(HooksManager::unloadLibraries());
}
// This test verifies that parked objects are removed when libraries are
// unloaded.
TEST_F(HooksManagerTest, UnloadBeforeUnpark) {
// Load the same library twice. Both installed callouts will trigger
// asynchronous operation.
HookLibsCollection library_names;
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
data::ConstElementPtr()));
// Load libraries.
EXPECT_TRUE(HooksManager::loadLibraries(library_names));
CalloutHandlePtr handle = HooksManager::createCalloutHandle();
// We could be parked any object. Typically it will be a pointer to the
// packet. In this case, however, it is simpler to just use a string.
std::string parked_object = "foo";
handle->setArgument("parked_object", parked_object);
// Call installed callout.
HooksManager::callCallouts(hookpt_one_index_, *handle);
// This boolean value will be set to true when the packet gets unparked.
bool unparked = false;
// The callouts instruct us to park the object. We associated the callback
// function with the parked object, which sets "unparked" flag to true. We
// can later test the value of this flag to verify when exactly the packet
// got unparked.
HooksManager::park<std::string>("hookpt_one", "foo",
[&unparked] {
unparked = true;
});
// Try reloading the libraries.
EXPECT_NO_THROW(HooksManager::unloadLibraries());
EXPECT_TRUE(HooksManager::loadLibraries(library_names));
// Parked object should have been removed.
EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
// Callback should not be called.
EXPECT_FALSE(unparked);
}
} // Anonymous namespace } // Anonymous namespace

View File

@ -0,0 +1,112 @@
// 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 <exceptions/exceptions.h>
#include <hooks/parking_lots.h>
#include <gtest/gtest.h>
#include <string>
using namespace isc;
using namespace isc::hooks;
namespace {
// Test that it is possible to create and retrieve parking lots for
// specified hook points.
TEST(ParkingLotsTest, createGetParkingLot) {
ParkingLots parking_lots;
ParkingLotPtr parking_lot0 = parking_lots.getParkingLotPtr(1);
ParkingLotPtr parking_lot1 = parking_lots.getParkingLotPtr(2);
ParkingLotPtr parking_lot2 = parking_lots.getParkingLotPtr(1);
ASSERT_TRUE(parking_lot0);
ASSERT_TRUE(parking_lot1);
ASSERT_TRUE(parking_lot2);
EXPECT_FALSE(parking_lot0 == parking_lot1);
EXPECT_TRUE(parking_lot0 == parking_lot2);
ASSERT_NO_THROW(parking_lots.clear());
ParkingLotPtr parking_lot3 = parking_lots.getParkingLotPtr(1);
ASSERT_TRUE(parking_lot3);
EXPECT_FALSE(parking_lot3 == parking_lot0);
}
// Test that object can't be parked if it hasn't been referenced on the
// parking lot.
TEST(ParkingLotTest, parkWithoutReferencing) {
ParkingLot parking_lot;
std::string parked_object = "foo";
EXPECT_THROW(parking_lot.park(parked_object, [] {
}), InvalidOperation);
}
// Test that object can be parked and then unparked.
TEST(ParkingLotTest, unpark) {
ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
ParkingLotHandlePtr parking_lot_handle =
boost::make_shared<ParkingLotHandle>(parking_lot);
std::string parked_object = "foo";
// Reference the parked object twice because we're going to test that
// reference counting works fine.
ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
// This flag will indicate if the callback has been called.
bool unparked = false;
ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
unparked = true;
}));
// Try to unpark the object. It should decrease the reference count, but not
// unpark the packet yet.
EXPECT_TRUE(parking_lot_handle->unpark(parked_object));
EXPECT_FALSE(unparked);
// Try to unpark the object. This time it should be successful, because the
// reference count goes to 0.
EXPECT_TRUE(parking_lot_handle->unpark(parked_object));
EXPECT_TRUE(unparked);
// Calling unpark again should return false to indicate that the object is
// not parked.
EXPECT_FALSE(parking_lot_handle->unpark(parked_object));
}
// Test that parked object can be dropped.
TEST(ParkingLotTest, drop) {
ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
ParkingLotHandlePtr parking_lot_handle =
boost::make_shared<ParkingLotHandle>(parking_lot);
std::string parked_object = "foo";
// Reference object twice to test that dropping the packet ignores
// reference counting.
ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
// This flag will indicate if the callback has been called.
bool unparked = false;
ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
unparked = true;
}));
// Drop parked object. The callback should not be invoked.
EXPECT_TRUE(parking_lot_handle->drop(parked_object));
EXPECT_FALSE(unparked);
// Expect that an attempt to unpark return false, as the object
// has been dropped.
EXPECT_FALSE(parking_lot_handle->unpark(parked_object));
}
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@ -219,4 +219,14 @@ TEST(ServerHooksTest, HookToCommandName) {
EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty()); EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty());
} }
TEST(ServerHooksTest, getParkingLots) {
ServerHooks& hooks = ServerHooks::getServerHooks();
hooks.reset();
int alpha_hook = hooks.registerHook("alpha");
ASSERT_TRUE(hooks.getParkingLotsPtr());
ASSERT_TRUE(hooks.getParkingLotPtr(alpha_hook));
ASSERT_TRUE(hooks.getParkingLotPtr("alpha"));
}
} // Anonymous namespace } // Anonymous namespace

View File

@ -49,6 +49,10 @@ static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl.so";
// Library where parameters are checked. // Library where parameters are checked.
static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libpcl.so"; static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libpcl.so";
// Library which tests objects parking.
static const char* ASYNC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libacl.so";
} // anonymous namespace } // anonymous namespace