mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 13:07:50 +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:
commit
af43f07b0e
@ -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
|
||||
// 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 <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/cfg_db_access.h>
|
||||
#include <hooks/hooks.h>
|
||||
#include <hooks/hooks_manager.h>
|
||||
#include <stats/stats_mgr.h>
|
||||
#include <cfgrpt/config_report.h>
|
||||
@ -30,6 +31,23 @@ using namespace std;
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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()));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
- @b Arguments:
|
||||
@ -215,6 +232,37 @@ to the end of this list.
|
||||
marked this lease as unavailable. If the client restarts its configuration,
|
||||
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
|
||||
|
||||
- @b Arguments:
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include <dhcp4/dhcp4_log.h>
|
||||
#include <dhcp4/dhcp4_srv.h>
|
||||
#include <dhcpsrv/addr_utilities.h>
|
||||
#include <dhcpsrv/callout_handle_store.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/cfg_host_operations.h>
|
||||
#include <dhcpsrv/cfg_iface.h>
|
||||
@ -79,36 +78,43 @@ using namespace isc::log;
|
||||
using namespace isc::stats;
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Structure that holds registered hook indexes
|
||||
struct Dhcp4Hooks {
|
||||
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_subnet4_select_; ///< index for "subnet4_select" 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_buffer4_send_; ///< index for "buffer4_send" 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_buffer4_receive_; ///< index for "buffer4_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_leases4_committed_; ///< index for "leases4_committed" 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_buffer4_send_; ///< index for "buffer4_send" hook point
|
||||
int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
|
||||
int hook_index_host4_identifier_; ///< index for "host4_identifier" hook point
|
||||
|
||||
/// Constructor that registers hook points for DHCPv4 engine
|
||||
Dhcp4Hooks() {
|
||||
hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
|
||||
hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
|
||||
hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
|
||||
hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
|
||||
hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
|
||||
hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
|
||||
hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
|
||||
hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
|
||||
hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
|
||||
hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
|
||||
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_lease4_release_ = HooksManager::registerHook("lease4_release");
|
||||
hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
|
||||
hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
|
||||
hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
|
||||
}
|
||||
};
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
// 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.
|
||||
Dhcp4Hooks Hooks;
|
||||
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
@ -834,72 +840,12 @@ Dhcpv4Srv::run_one() {
|
||||
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);
|
||||
|
||||
// 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());
|
||||
}
|
||||
CalloutHandlePtr callout_handle = getCalloutHandle(query);
|
||||
processPacketBufferSend(callout_handle, rsp);
|
||||
}
|
||||
|
||||
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
|
||||
// failures in unpacking will cause the packet to be dropped. We
|
||||
// will increase type specific statistic further down the road.
|
||||
@ -1047,6 +993,8 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
|
||||
callout_handle->getArgument("query4", query);
|
||||
}
|
||||
|
||||
AllocEngine::ClientContext4Ptr ctx;
|
||||
|
||||
try {
|
||||
switch (query->getType()) {
|
||||
case DHCPDISCOVER:
|
||||
@ -1057,15 +1005,15 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
|
||||
// Note that REQUEST is used for many things in DHCPv4: for
|
||||
// requesting new leases, renewing existing ones and even
|
||||
// for rebinding.
|
||||
rsp = processRequest(query);
|
||||
rsp = processRequest(query, ctx);
|
||||
break;
|
||||
|
||||
case DHCPRELEASE:
|
||||
processRelease(query);
|
||||
processRelease(query, ctx);
|
||||
break;
|
||||
|
||||
case DHCPDECLINE:
|
||||
processDecline(query);
|
||||
processDecline(query, ctx);
|
||||
break;
|
||||
|
||||
case DHCPINFORM:
|
||||
@ -1097,6 +1045,84 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@ -1106,7 +1132,6 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
|
||||
|
||||
// Execute all callouts registered for pkt4_send
|
||||
if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
|
||||
CalloutHandlePtr callout_handle = getCalloutHandle(query);
|
||||
|
||||
// Delete all previous arguments
|
||||
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
|
||||
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
|
||||
if (!srvid) {
|
||||
@ -2440,7 +2535,7 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
|
||||
}
|
||||
|
||||
Pkt4Ptr
|
||||
Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
|
||||
Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context) {
|
||||
/// @todo Uncomment this (see ticket #3116)
|
||||
/// sanityCheck(request, MANDATORY);
|
||||
|
||||
@ -2492,11 +2587,15 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
|
||||
|
||||
appendServerID(ex);
|
||||
|
||||
// Return the pointer to the context, which will be required by the
|
||||
// leases4_comitted callouts.
|
||||
context = ex.getContext();
|
||||
|
||||
return (ex.getResponse());
|
||||
}
|
||||
|
||||
void
|
||||
Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
|
||||
Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context) {
|
||||
/// @todo Uncomment this (see ticket #3116)
|
||||
/// sanityCheck(release, MANDATORY);
|
||||
|
||||
@ -2575,6 +2674,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
|
||||
bool success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
|
||||
|
||||
if (success) {
|
||||
|
||||
context.reset(new AllocEngine::ClientContext4());
|
||||
context->old_lease_ = lease;
|
||||
|
||||
// Release successful
|
||||
LOG_INFO(lease4_logger, DHCP4_RELEASE)
|
||||
.arg(release->getLabel())
|
||||
@ -2604,7 +2707,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
|
||||
}
|
||||
|
||||
void
|
||||
Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
|
||||
Dhcpv4Srv::processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context) {
|
||||
|
||||
// Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
|
||||
/// @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
|
||||
// process it.
|
||||
declineLease(lease, decline);
|
||||
declineLease(lease, decline, context);
|
||||
}
|
||||
|
||||
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.
|
||||
// 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);
|
||||
|
||||
context.reset(new AllocEngine::ClientContext4());
|
||||
context->new_lease_ = lease;
|
||||
|
||||
LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
|
||||
.arg(decline->getLabel()).arg(lease->valid_lft_);
|
||||
}
|
||||
|
@ -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
|
||||
// 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 <dhcpsrv/alloc_engine.h>
|
||||
#include <dhcpsrv/cfg_option.h>
|
||||
#include <dhcpsrv/callout_handle_store.h>
|
||||
#include <dhcpsrv/d2_client_mgr.h>
|
||||
#include <dhcpsrv/network_state.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
@ -25,6 +26,7 @@
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
|
||||
@ -259,7 +261,10 @@ public:
|
||||
///
|
||||
/// @param query A pointer to the packet to be processed.
|
||||
/// @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.
|
||||
void shutdown();
|
||||
@ -446,9 +451,11 @@ protected:
|
||||
/// Returns ACK message, NAK message, or NULL
|
||||
///
|
||||
/// @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
|
||||
Pkt4Ptr processRequest(Pkt4Ptr& request);
|
||||
Pkt4Ptr processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context);
|
||||
|
||||
/// @brief Processes incoming DHCPRELEASE messages.
|
||||
///
|
||||
@ -456,7 +463,9 @@ protected:
|
||||
/// this function does not return anything.
|
||||
///
|
||||
/// @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.
|
||||
///
|
||||
@ -465,7 +474,9 @@ protected:
|
||||
/// the client and if it does, calls @ref declineLease.
|
||||
///
|
||||
/// @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.
|
||||
///
|
||||
@ -650,7 +661,9 @@ private:
|
||||
///
|
||||
/// @param lease lease to be declined
|
||||
/// @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:
|
||||
|
||||
@ -819,6 +832,21 @@ protected:
|
||||
/// @param query Pointer to the client message.
|
||||
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.
|
||||
/// Pointer to the allocation engine that we are currently using
|
||||
/// It must be a pointer, because we will support changing engines
|
||||
|
@ -103,7 +103,7 @@ void Dhcp4to6Ipc::handler() {
|
||||
// From Dhcpv4Srv::run_one() processing and after
|
||||
Pkt4Ptr rsp;
|
||||
|
||||
ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp);
|
||||
ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp, false);
|
||||
|
||||
if (!rsp) {
|
||||
return;
|
||||
|
@ -56,7 +56,7 @@ if HAVE_GTEST
|
||||
# to unexpected errors. For this reason, the --enable-static-link option is
|
||||
# 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
|
||||
# convenience archive, resulting in shared modules
|
||||
@ -71,6 +71,11 @@ libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
|
||||
libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
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
|
||||
|
||||
dhcp4_unittests_SOURCES = d2_unittest.h d2_unittest.cc
|
||||
|
@ -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
|
||||
// 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;
|
||||
#include <config.h>
|
||||
#include <dhcp4/tests/callout_library_common.h>
|
||||
|
||||
#include "callout_library_common.h"
|
||||
|
@ -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
|
||||
// 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;
|
||||
#include <config.h>
|
||||
#include <dhcp4/tests/callout_library_common.h>
|
||||
|
||||
#include "callout_library_common.h"
|
||||
|
44
src/bin/dhcp4/tests/callout_library_3.cc
Normal file
44
src/bin/dhcp4/tests/callout_library_3.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
// 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,
|
||||
/// 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
|
||||
/// by itself - it is included into each callout library which specifies the
|
||||
/// missing constant LIBRARY_NUMBER before the inclusion.
|
||||
@ -53,6 +57,26 @@ appendDigit(const char* name) {
|
||||
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
|
||||
int
|
||||
version() {
|
||||
|
@ -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
|
||||
Dhcp4Client::includeClientId(const std::string& clientid) {
|
||||
if (clientid.empty()) {
|
||||
@ -519,7 +528,14 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
|
||||
msg_copy->setLocalAddr(dest_addr_);
|
||||
msg_copy->setIface(iface_name_);
|
||||
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
|
||||
|
@ -206,6 +206,15 @@ public:
|
||||
/// DHCPACK message is applied and can be obtained from the @c config_.
|
||||
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.
|
||||
///
|
||||
/// It assigns random values to the bytes of the hardware address.
|
||||
|
@ -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
|
||||
// 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/pkt_filter.h>
|
||||
#include <dhcp/pkt_filter_inet.h>
|
||||
#include <dhcpsrv/alloc_engine.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
#include <dhcpsrv/lease.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
@ -170,6 +171,31 @@ public:
|
||||
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.
|
||||
OptionPtr server_id_;
|
||||
|
||||
|
@ -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
|
||||
// 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_client.h>
|
||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <asiolink/io_service.h>
|
||||
#include <cc/command_interpreter.h>
|
||||
#include <config/command_mgr.h>
|
||||
#include <hooks/server_hooks.h>
|
||||
@ -18,6 +20,7 @@
|
||||
#include <dhcp/tests/iface_mgr_test_config.h>
|
||||
#include <dhcp/option.h>
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp4/tests/dhcp4_client.h>
|
||||
#include <dhcp4/tests/marker_file.h>
|
||||
#include <dhcp4/tests/test_libraries.h>
|
||||
|
||||
@ -36,13 +39,14 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
|
||||
NakedDhcpv4Srv srv(0);
|
||||
|
||||
// check if appropriate hooks are registered
|
||||
int hook_index_buffer4_receive = -1;
|
||||
int hook_index_pkt4_receive = -1;
|
||||
int hook_index_select_subnet = -1;
|
||||
int hook_index_lease4_release = -1;
|
||||
int hook_index_pkt4_send = -1;
|
||||
int hook_index_buffer4_send = -1;
|
||||
int hook_index_host4_identifier = -1;
|
||||
int hook_index_buffer4_receive = -1;
|
||||
int hook_index_pkt4_receive = -1;
|
||||
int hook_index_select_subnet = -1;
|
||||
int hook_index_leases4_committed = -1;
|
||||
int hook_index_lease4_release = -1;
|
||||
int hook_index_pkt4_send = -1;
|
||||
int hook_index_buffer4_send = -1;
|
||||
int hook_index_host4_identifier = -1;
|
||||
|
||||
// check if appropriate indexes are set
|
||||
EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
|
||||
@ -51,6 +55,8 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
|
||||
.getIndex("pkt4_receive"));
|
||||
EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
|
||||
.getIndex("subnet4_select"));
|
||||
EXPECT_NO_THROW(hook_index_leases4_committed = ServerHooks::getServerHooks()
|
||||
.getIndex("leases4_committed"));
|
||||
EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks()
|
||||
.getIndex("lease4_release"));
|
||||
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_pkt4_receive > 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_pkt4_send > 0);
|
||||
EXPECT_TRUE(hook_index_buffer4_send > 0);
|
||||
@ -102,11 +109,14 @@ public:
|
||||
|
||||
// clear static buffers
|
||||
resetCalloutBuffers();
|
||||
|
||||
io_service_ = boost::make_shared<IOService>();
|
||||
}
|
||||
|
||||
/// @brief destructor (deletes Dhcpv4Srv)
|
||||
virtual ~HooksDhcpv4SrvTest() {
|
||||
|
||||
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("dhcp4_srv_configured");
|
||||
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive");
|
||||
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
|
||||
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive");
|
||||
@ -124,7 +134,7 @@ public:
|
||||
/// @brief creates an option with specified option code
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
@ -225,7 +235,7 @@ public:
|
||||
|
||||
// If there is at least one option with data
|
||||
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
|
||||
pkt->data_[28] = 0xff;
|
||||
}
|
||||
@ -621,6 +631,76 @@ public:
|
||||
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"
|
||||
///
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
@ -683,6 +763,7 @@ public:
|
||||
callback_qry_pkt4_.reset();
|
||||
callback_qry_pkt4_.reset();
|
||||
callback_lease4_.reset();
|
||||
callback_deleted_lease4_.reset();
|
||||
callback_hwaddr_.reset();
|
||||
callback_clientid_.reset();
|
||||
callback_subnet4_.reset();
|
||||
@ -695,6 +776,9 @@ public:
|
||||
/// pointer to Dhcpv4Srv that is used in tests
|
||||
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
|
||||
|
||||
/// String name of the received callout
|
||||
@ -706,9 +790,12 @@ public:
|
||||
/// Server/response Pkt4 structure returned in the callout
|
||||
static Pkt4Ptr callback_resp_pkt4_;
|
||||
|
||||
/// Lease4 structure returned in the callout
|
||||
/// Lease4 structure returned in the leases4_committed callout
|
||||
static Lease4Ptr callback_lease4_;
|
||||
|
||||
/// Lease4 structure returned in the leases4_committed callout
|
||||
static Lease4Ptr callback_deleted_lease4_;
|
||||
|
||||
/// Hardware address returned in the callout
|
||||
static HWAddrPtr callback_hwaddr_;
|
||||
|
||||
@ -736,6 +823,7 @@ public:
|
||||
|
||||
// The following fields are used in testing pkt4_receive_callout.
|
||||
// See fields description in the class for details
|
||||
IOServicePtr HooksDhcpv4SrvTest::io_service_;
|
||||
string HooksDhcpv4SrvTest::callback_name_;
|
||||
Pkt4Ptr HooksDhcpv4SrvTest::callback_qry_pkt4_;
|
||||
Pkt4Ptr HooksDhcpv4SrvTest::callback_resp_pkt4_;
|
||||
@ -743,6 +831,7 @@ Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
|
||||
HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_;
|
||||
ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_;
|
||||
Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_;
|
||||
Lease4Ptr HooksDhcpv4SrvTest::callback_deleted_lease4_;
|
||||
const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
|
||||
vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
|
||||
bool HooksDhcpv4SrvTest::callback_qry_options_copy_;
|
||||
@ -775,10 +864,12 @@ public:
|
||||
// Get rid of any marker files.
|
||||
static_cast<void>(remove(LOAD_MARKER_FILE));
|
||||
static_cast<void>(remove(UNLOAD_MARKER_FILE));
|
||||
static_cast<void>(remove(SRV_CONFIG_MARKER_FILE));
|
||||
CfgMgr::instance().clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Checks if callouts installed on pkt4_receive are indeed called and the
|
||||
// all necessary parameters are passed.
|
||||
//
|
||||
@ -1526,29 +1617,46 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
|
||||
EXPECT_TRUE((*subnets)[1]->inPool(Lease::TYPE_V4, addr));
|
||||
}
|
||||
|
||||
// Checks that subnet4_select is able to drop the packet.
|
||||
TEST_F(HooksDhcpv4SrvTest, subnet4SelectDrop) {
|
||||
// This test verifies that the leases4_committed hook point is not triggered
|
||||
// for the DHCPDISCOVER.
|
||||
TEST_F(HooksDhcpv4SrvTest, leases4CommittedDiscover) {
|
||||
IfaceMgrTestConfig test_config(true);
|
||||
IfaceMgr::instance().openSockets4();
|
||||
|
||||
// Install subnet4_select_drop callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"subnet4_select", subnet4_select_drop_callout));
|
||||
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"leases4_committed", leases4_committed_callout));
|
||||
|
||||
// Let's create a simple DISCOVER
|
||||
Pkt4Ptr discover = generateSimpleDiscover();
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(discover);
|
||||
Dhcp4Client client(Dhcp4Client::SELECTING);
|
||||
client.setIfaceName("eth1");
|
||||
ASSERT_NO_THROW(client.doDiscover());
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive4(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered subnet4_select callback.
|
||||
srv_->run();
|
||||
// Make sure that we received a response
|
||||
ASSERT_TRUE(client.getContext().response_);
|
||||
|
||||
// Check that there is no packet sent
|
||||
EXPECT_EQ(0, srv_->fake_sent_.size());
|
||||
// Make sure that the callout wasn't called.
|
||||
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
|
||||
@ -1719,6 +1827,189 @@ TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
|
||||
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
|
||||
TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
|
||||
IfaceMgrTestConfig test_config(true);
|
||||
@ -1873,6 +2164,50 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
|
||||
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
|
||||
// lease4_release hook point will keep the lease
|
||||
TEST_F(HooksDhcpv4SrvTest, lease4ReleaseDrop) {
|
||||
@ -2083,6 +2418,50 @@ TEST_F(HooksDhcpv4SrvTest, HooksDeclineDrop) {
|
||||
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
|
||||
// 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(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"));
|
||||
}
|
||||
|
@ -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
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -14,6 +14,7 @@
|
||||
namespace {
|
||||
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 SRV_CONFIG_MARKER_FILE = "@abs_builddir@/srv_config_marker_file.txt";
|
||||
}
|
||||
|
||||
namespace isc {
|
||||
|
@ -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
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -21,6 +21,7 @@ namespace {
|
||||
// operation.
|
||||
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_3 = "@abs_builddir@/.libs/libco3.so";
|
||||
|
||||
// Name of a library which is not present.
|
||||
const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
|
||||
|
@ -1468,9 +1468,9 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
|
||||
}
|
||||
|
||||
/// DROP status does not make sense here:
|
||||
/// In general as the lease cannot be dropped the DROP action
|
||||
/// has no object so SKIP is the right "cancel" status and
|
||||
/// DROP should not be a synonym as it introduces ambiguity.
|
||||
/// In general as the lease cannot be dropped the DROP action
|
||||
/// has no object so SKIP is the right "cancel" status and
|
||||
/// DROP should not be a synonym as it introduces ambiguity.
|
||||
|
||||
// Let's use whatever callout returned. Hopefully it is the same lease
|
||||
// we handed to it.
|
||||
@ -2734,8 +2734,8 @@ AllocEngine::ClientContext4::ClientContext4()
|
||||
requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
|
||||
fwd_dns_update_(false), rev_dns_update_(false),
|
||||
hostname_(""), callout_handle_(), fake_allocation_(false),
|
||||
old_lease_(), hosts_(), conflicting_lease_(), query_(),
|
||||
host_identifiers_() {
|
||||
old_lease_(), new_lease_(), hosts_(), conflicting_lease_(),
|
||||
query_(), host_identifiers_() {
|
||||
}
|
||||
|
||||
AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
|
||||
@ -2750,8 +2750,8 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
|
||||
requested_address_(requested_addr),
|
||||
fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
|
||||
hostname_(hostname), callout_handle_(),
|
||||
fake_allocation_(fake_allocation), old_lease_(), hosts_(),
|
||||
host_identifiers_() {
|
||||
fake_allocation_(fake_allocation), old_lease_(), new_lease_(),
|
||||
hosts_(), host_identifiers_() {
|
||||
|
||||
// Initialize host identifiers.
|
||||
if (hwaddr) {
|
||||
@ -2776,8 +2776,7 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
|
||||
// be later set to non NULL value if existing lease is found in the
|
||||
// database.
|
||||
ctx.old_lease_.reset();
|
||||
|
||||
Lease4Ptr new_lease;
|
||||
ctx.new_lease_.reset();
|
||||
|
||||
// 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
|
||||
@ -2798,7 +2797,12 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
|
||||
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) {
|
||||
// Some other error, return an empty lease.
|
||||
@ -2807,7 +2811,7 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
|
||||
.arg(e.what());
|
||||
}
|
||||
|
||||
return (new_lease);
|
||||
return (ctx.new_lease_);
|
||||
}
|
||||
|
||||
void
|
||||
@ -2992,6 +2996,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
|
||||
if (!ctx.old_lease_ && client_lease) {
|
||||
ctx.old_lease_ = client_lease;
|
||||
}
|
||||
|
||||
return (new_lease);
|
||||
}
|
||||
|
||||
|
@ -1104,6 +1104,9 @@ public:
|
||||
/// @brief A pointer to an old lease that the client had before update.
|
||||
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
|
||||
/// subnets.
|
||||
///
|
||||
|
@ -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
|
||||
// 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.
|
||||
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
|
||||
///
|
||||
/// 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.
|
||||
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.
|
||||
///
|
||||
/// Dumps the output of Lease::toText to the given stream.
|
||||
|
@ -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_manager.cc library_manager.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 += server_hooks.cc server_hooks.h
|
||||
|
||||
|
@ -74,6 +74,11 @@ CalloutHandle::getArgumentNames() const {
|
||||
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
|
||||
// registration/deregistration functions.
|
||||
|
||||
|
@ -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
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <hooks/library_handle.h>
|
||||
#include <hooks/parking_lots.h>
|
||||
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@ -91,7 +92,8 @@ public:
|
||||
enum CalloutNextStep {
|
||||
NEXT_STEP_CONTINUE = 0, ///< continue normally
|
||||
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
|
||||
/// 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
|
||||
/// 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.
|
||||
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:
|
||||
|
||||
/// @brief Check index
|
||||
///
|
||||
/// Gets the current library index, throwing an exception if it is not set
|
||||
|
@ -130,6 +130,7 @@ HooksManager::unloadLibrariesInternal() {
|
||||
// ease debugging.
|
||||
lm_collection_.reset();
|
||||
callout_manager_.reset();
|
||||
ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
|
||||
}
|
||||
|
||||
void HooksManager::unloadLibraries() {
|
||||
|
@ -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
|
||||
// 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
|
||||
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:
|
||||
|
||||
/// @brief Constructor
|
||||
@ -242,6 +318,55 @@ private:
|
||||
/// through the getHooksManager() static method.
|
||||
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,
|
||||
/// but actually do the work on the singleton instance of the HooksManager.
|
||||
|
333
src/lib/hooks/parking_lots.h
Normal file
333
src/lib/hooks/parking_lots.h
Normal 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
|
@ -84,6 +84,7 @@ ServerHooks::initialize() {
|
||||
// Clear out the name->index and index->name maps.
|
||||
hooks_.clear();
|
||||
inverse_hooks_.clear();
|
||||
parking_lots_.reset(new ParkingLots());
|
||||
|
||||
// Register the pre-defined hooks.
|
||||
int create = registerHook("context_create");
|
||||
@ -175,6 +176,21 @@ ServerHooks::getServerHooksPtr() {
|
||||
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
|
||||
ServerHooks::commandToHookName(const std::string& command_name) {
|
||||
// Prefix the command name with a dollar sign.
|
||||
|
@ -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
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -8,6 +8,7 @@
|
||||
#define SERVER_HOOKS_H
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <hooks/parking_lots.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@ -152,6 +153,25 @@ public:
|
||||
/// @return Pointer to the global ServerHooks object.
|
||||
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.
|
||||
///
|
||||
/// This function is called to generate the name of the hook point
|
||||
@ -216,6 +236,8 @@ private:
|
||||
/// simpler than using a multi-indexed container.)
|
||||
HookCollection hooks_; ///< Hook name/index collection
|
||||
InverseHookCollection inverse_hooks_; ///< Hook index/name collection
|
||||
|
||||
ParkingLotsPtr parking_lots_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
|
@ -40,7 +40,7 @@ if HAVE_GTEST
|
||||
# ignored for unit tests built here.
|
||||
|
||||
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
|
||||
# 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 += $(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
|
||||
run_unittests_SOURCES = run_unittests.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 += library_manager_collection_unittest.cc
|
||||
run_unittests_SOURCES += library_manager_unittest.cc
|
||||
run_unittests_SOURCES += parking_lots_unittest.cc
|
||||
run_unittests_SOURCES += server_hooks_unittest.cc
|
||||
|
||||
nodist_run_unittests_SOURCES = marker_file.h
|
||||
|
153
src/lib/hooks/tests/async_callout_library.cc
Normal file
153
src/lib/hooks/tests/async_callout_library.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
// 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 NO("N");
|
||||
static const std::string DROP("D");
|
||||
static const std::string PARK("P");
|
||||
|
||||
switch (handle.getStatus()) {
|
||||
case CalloutHandle::NEXT_STEP_CONTINUE:
|
||||
@ -761,6 +762,9 @@ calloutPrintSkip(CalloutHandle& handle) {
|
||||
case CalloutHandle::NEXT_STEP_DROP:
|
||||
HandlesTest::common_string_ += DROP; // drop
|
||||
break;
|
||||
case CalloutHandle::NEXT_STEP_PARK:
|
||||
HandlesTest::common_string_ += PARK; // park
|
||||
break;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
@ -893,6 +897,11 @@ calloutSetArgumentDrop(CalloutHandle& handle) {
|
||||
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
|
||||
// but otherwise not alter it.
|
||||
|
||||
@ -920,7 +929,9 @@ TEST_F(HandlesTest, CheckModifiedArgument) {
|
||||
getCalloutManager()->setLibraryIndex(2);
|
||||
getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip);
|
||||
getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue);
|
||||
getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark);
|
||||
getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip);
|
||||
getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark);
|
||||
|
||||
// Create the argument with an initial empty string value. Then call the
|
||||
// sequence of callouts above.
|
||||
@ -935,7 +946,7 @@ TEST_F(HandlesTest, CheckModifiedArgument) {
|
||||
EXPECT_EQ(std::string("SCC" "SD"), common_string_);
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
// 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());
|
||||
}
|
||||
|
||||
// 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
|
||||
|
112
src/lib/hooks/tests/parking_lots_unittest.cc
Normal file
112
src/lib/hooks/tests/parking_lots_unittest.cc
Normal 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));
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
// 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());
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -49,6 +49,10 @@ static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl.so";
|
||||
// Library where parameters are checked.
|
||||
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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user