diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc new file mode 100644 index 0000000..935296d --- /dev/null +++ b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc @@ -0,0 +1,660 @@ +// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcpsrv/tests/alloc_engine_utils.h> +#include <dhcpsrv/testutils/test_utils.h> + +#include <hooks/server_hooks.h> +#include <hooks/callout_manager.h> +#include <hooks/hooks_manager.h> + +#include <iostream> + +using namespace std; +using namespace isc::hooks; +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief helper class used in Hooks testing in AllocEngine6 +/// +/// It features a couple of callout functions and buffers to store +/// the data that is accessible via callouts. +class HookAllocEngine6Test : public AllocEngine6Test { +public: + HookAllocEngine6Test() { + resetCalloutBuffers(); + } + + virtual ~HookAllocEngine6Test() { + resetCalloutBuffers(); + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts( + "lease6_select"); + bool status = HooksManager::unloadLibraries(); + if (!status) { + cerr << "(fixture dtor) unloadLibraries failed" << endl; + } + } + + /// @brief clears out buffers, so callouts can store received arguments + void resetCalloutBuffers() { + callback_name_ = string(""); + callback_subnet6_.reset(); + callback_fake_allocation_ = false; + callback_lease6_.reset(); + callback_argument_names_.clear(); + callback_addr_original_ = IOAddress("::"); + callback_addr_updated_ = IOAddress("::"); + callback_qry_pkt6_.reset(); + callback_qry_options_copy_ = false; + callback_skip_ = 0; + } + + /// @brief Checks if the state of the callout handle associated with a query + /// was reset after the callout invocation. + /// + /// The check includes verification if the status was set to 'continue' and + /// that all arguments were deleted. + /// + /// @param query pointer to the query which callout handle is associated + /// with. + void checkCalloutHandleReset(const Pkt6Ptr& query) { + CalloutHandlePtr callout_handle = query->getCalloutHandle(); + ASSERT_TRUE(callout_handle); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus()); + EXPECT_TRUE(callout_handle->getArgumentNames().empty()); + } + + /// callback that stores received callout name and received values + static int + lease6_select_callout(CalloutHandle& callout_handle) { + + callback_name_ = string("lease6_select"); + + callout_handle.getArgument("query6", callback_qry_pkt6_); + callout_handle.getArgument("subnet6", callback_subnet6_); + callout_handle.getArgument("fake_allocation", callback_fake_allocation_); + callout_handle.getArgument("lease6", callback_lease6_); + + callback_addr_original_ = callback_lease6_->addr_; + + callback_argument_names_ = callout_handle.getArgumentNames(); + + if (callback_qry_pkt6_) { + callback_qry_options_copy_ = + callback_qry_pkt6_->isCopyRetrievedOptions(); + } + + return (0); + } + + /// callback that overrides the lease with different values + static int + lease6_select_different_callout(CalloutHandle& callout_handle) { + + // Let's call the basic callout, so it can record all parameters + lease6_select_callout(callout_handle); + + // Now we need to tweak the least a bit + Lease6Ptr lease; + callout_handle.getArgument("lease6", lease); + callback_addr_updated_ = addr_override_; + lease->addr_ = callback_addr_updated_; + lease->preferred_lft_ = pref_override_; + lease->valid_lft_ = valid_override_; + + return (0); + } + + /// callback that return next step skip status + static int + lease6_select_skip_callout(CalloutHandle& callout_handle) { + + // Let's call the basic callout, so it can record all parameters + lease6_select_callout(callout_handle); + + // Count the call + callback_skip_++; + + callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + + return (0); + } + + // Values to be used in callout to override lease6 content + static const IOAddress addr_override_; + static const uint32_t pref_override_; + static const uint32_t valid_override_; + + // Callback will store original and overridden values here + static IOAddress callback_addr_original_; + static IOAddress callback_addr_updated_; + + // Buffers (callback will store received values here) + static string callback_name_; + static Subnet6Ptr callback_subnet6_; + static Lease6Ptr callback_lease6_; + static bool callback_fake_allocation_; + static vector<string> callback_argument_names_; + static Pkt6Ptr callback_qry_pkt6_; + static bool callback_qry_options_copy_; + + // Counter for next step skip (should be not retried) + static unsigned callback_skip_; +}; + +// For some reason initialization within a class makes the linker confused. +// linker complains about undefined references if they are defined within +// the class declaration. +const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd"); +const uint32_t HookAllocEngine6Test::pref_override_ = 8000; +const uint32_t HookAllocEngine6Test::valid_override_ = 9000; + +IOAddress HookAllocEngine6Test::callback_addr_original_("::"); +IOAddress HookAllocEngine6Test::callback_addr_updated_("::"); + +string HookAllocEngine6Test::callback_name_; +Subnet6Ptr HookAllocEngine6Test::callback_subnet6_; +Lease6Ptr HookAllocEngine6Test::callback_lease6_; +bool HookAllocEngine6Test::callback_fake_allocation_; +vector<string> HookAllocEngine6Test::callback_argument_names_; +Pkt6Ptr HookAllocEngine6Test::callback_qry_pkt6_; +bool HookAllocEngine6Test::callback_qry_options_copy_; + +unsigned HookAllocEngine6Test::callback_skip_; + +// This test checks if the lease6_select callout is executed and expected +// parameters as passed. +TEST_F(HookAllocEngine6Test, lease6_select) { + + // Note: The following order is working as expected: + // 1. create AllocEngine (that register hook points) + // 2. call loadLibraries() + // + // This order, however, causes segfault in HooksManager + // 1. call loadLibraries() + // 2. create AllocEngine (that register hook points) + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install lease6_select + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease6_select", lease6_select_callout)); + + Lease6Ptr lease; + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)), + HooksManager::createCalloutHandle()); + ctx.currentIA().iaid_ = iaid_; + + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + // Check that we got a lease + ASSERT_TRUE(lease); + + // Do all checks on the lease + checkLease6(duid_, lease, Lease::TYPE_NA, 128); + + // Check that the lease is indeed in LeaseMgr + Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + ASSERT_TRUE(from_mgr); + + // Check that callouts were indeed called + EXPECT_EQ("lease6_select", callback_name_); + + // Check that query6 argument was set correctly + ASSERT_TRUE(callback_qry_pkt6_); + EXPECT_EQ(callback_qry_pkt6_.get(), ctx.query_.get()); + + // Now check that the lease in LeaseMgr has the same parameters + ASSERT_TRUE(callback_lease6_); + detailCompareLease(callback_lease6_, from_mgr); + + ASSERT_TRUE(callback_subnet6_); + EXPECT_EQ(subnet_->toText(), callback_subnet6_->toText()); + + EXPECT_FALSE(callback_fake_allocation_); + + // Check if all expected parameters are reported. The order needs to be + // alphabetical to match the order returned by + // CallbackHandle::getArgumentNames() + vector<string> expected_argument_names; + expected_argument_names.push_back("fake_allocation"); + expected_argument_names.push_back("lease6"); + expected_argument_names.push_back("query6"); + expected_argument_names.push_back("subnet6"); + + sort(callback_argument_names_.begin(), callback_argument_names_.end()); + sort(expected_argument_names.begin(), expected_argument_names.end()); + + EXPECT_TRUE(callback_argument_names_ == expected_argument_names); + + EXPECT_TRUE(callback_qry_options_copy_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +// This test checks if lease6_select callout is able to override the values +// in a lease6. +TEST_F(HookAllocEngine6Test, change_lease6_select) { + + // Make sure that the overridden values are different than the ones from + // subnet originally used to create the lease + ASSERT_NE(pref_override_, subnet_->getPreferred()); + ASSERT_NE(valid_override_, subnet_->getValid()); + ASSERT_FALSE(subnet_->inRange(addr_override_)); + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install a callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease6_select", lease6_select_different_callout)); + + // Call allocateLeases6. Callouts should be triggered here. + Lease6Ptr lease; + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)), + HooksManager::createCalloutHandle()); + ctx.currentIA().iaid_ = iaid_; + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + // Check that we got a lease + ASSERT_TRUE(lease); + + // See if the values overridden by callout are there + EXPECT_TRUE(lease->addr_.equals(addr_override_)); + EXPECT_EQ(pref_override_, lease->preferred_lft_); + EXPECT_EQ(valid_override_, lease->valid_lft_); + + // Now check if the lease is in the database + Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + ASSERT_TRUE(from_mgr); + + // Check if values in the database are overridden + EXPECT_TRUE(from_mgr->addr_.equals(addr_override_)); + EXPECT_EQ(pref_override_, from_mgr->preferred_lft_); + EXPECT_EQ(valid_override_, from_mgr->valid_lft_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +// This test checks if lease6_select callout can set the status to next +// step skip without the engine to retry. +TEST_F(HookAllocEngine6Test, skip_lease6_select) { + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install a callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease6_select", lease6_select_skip_callout)); + + // Call allocateLeases6. Callouts should be triggered here. + Lease6Ptr lease; + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)), + HooksManager::createCalloutHandle()); + ctx.currentIA().iaid_ = iaid_; + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + // Check that we got no lease + EXPECT_FALSE(lease); + + // Check no retry was attempted + EXPECT_EQ(1, callback_skip_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +/// @brief helper class used in Hooks testing in AllocEngine4 +/// +/// It features a couple of callout functions and buffers to store +/// the data that is accessible via callouts. +/// +/// Note: lease4_renew callout is tested from DHCPv4 server. +/// See HooksDhcpv4SrvTest.basic_lease4_renew in +/// src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +class HookAllocEngine4Test : public AllocEngine4Test { +public: + HookAllocEngine4Test() { + // The default context is not used in these tests. + ctx_.callout_handle_.reset(); + resetCalloutBuffers(); + } + + virtual ~HookAllocEngine4Test() { + resetCalloutBuffers(); + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts( + "lease4_select"); + bool status = HooksManager::unloadLibraries(); + if (!status) { + cerr << "(fixture dtor) unloadLibraries failed" << endl; + } + } + + /// @brief clears out buffers, so callouts can store received arguments + void resetCalloutBuffers() { + callback_name_ = string(""); + callback_subnet4_.reset(); + callback_fake_allocation_ = false; + callback_lease4_.reset(); + callback_argument_names_.clear(); + callback_addr_original_ = IOAddress("::"); + callback_addr_updated_ = IOAddress("::"); + callback_qry_pkt4_.reset(); + callback_qry_options_copy_ = false; + callback_skip_ = 0; + } + + /// @brief Checks if the state of the callout handle associated with a query + /// was reset after the callout invocation. + /// + /// The check includes verification if the status was set to 'continue' and + /// that all arguments were deleted. + /// + /// @param query pointer to the query which callout handle is associated + /// with. + void checkCalloutHandleReset(const Pkt4Ptr& query) { + CalloutHandlePtr callout_handle = query->getCalloutHandle(); + ASSERT_TRUE(callout_handle); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus()); + EXPECT_TRUE(callout_handle->getArgumentNames().empty()); + } + + /// callback that stores received callout name and received values + static int + lease4_select_callout(CalloutHandle& callout_handle) { + + callback_name_ = string("lease4_select"); + + callout_handle.getArgument("query4", callback_qry_pkt4_); + callout_handle.getArgument("subnet4", callback_subnet4_); + callout_handle.getArgument("fake_allocation", callback_fake_allocation_); + callout_handle.getArgument("lease4", callback_lease4_); + + callback_addr_original_ = callback_lease4_->addr_; + + callback_argument_names_ = callout_handle.getArgumentNames(); + + if (callback_qry_pkt4_) { + callback_qry_options_copy_ = + callback_qry_pkt4_->isCopyRetrievedOptions(); + } + + return (0); + } + + /// callback that overrides the lease with different values + static int + lease4_select_different_callout(CalloutHandle& callout_handle) { + + // Let's call the basic callout, so it can record all parameters + lease4_select_callout(callout_handle); + + // Now we need to tweak the least a bit + Lease4Ptr lease; + callout_handle.getArgument("lease4", lease); + callback_addr_updated_ = addr_override_; + lease->addr_ = callback_addr_updated_; + lease->valid_lft_ = valid_override_; + + return (0); + } + + /// callback that return next step skip status + static int + lease4_select_skip_callout(CalloutHandle& callout_handle) { + + // Let's call the basic callout, so it can record all parameters + lease4_select_callout(callout_handle); + + // Count the call + callback_skip_++; + + callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + + return (0); + } + + // Values to be used in callout to override lease4 content + static const IOAddress addr_override_; + static const uint32_t valid_override_; + + // Callback will store original and overridden values here + static IOAddress callback_addr_original_; + static IOAddress callback_addr_updated_; + + // Buffers (callback will store received values here) + static string callback_name_; + static Subnet4Ptr callback_subnet4_; + static Lease4Ptr callback_lease4_; + static bool callback_fake_allocation_; + static vector<string> callback_argument_names_; + static Pkt4Ptr callback_qry_pkt4_; + static bool callback_qry_options_copy_; + + // Counter for next step skip (should be not retried) + static unsigned callback_skip_; +}; + +// For some reason initialization within a class makes the linker confused. +// linker complains about undefined references if they are defined within +// the class declaration. +const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1"); +const uint32_t HookAllocEngine4Test::valid_override_ = 9000; + +IOAddress HookAllocEngine4Test::callback_addr_original_("::"); +IOAddress HookAllocEngine4Test::callback_addr_updated_("::"); + +string HookAllocEngine4Test::callback_name_; +Subnet4Ptr HookAllocEngine4Test::callback_subnet4_; +Lease4Ptr HookAllocEngine4Test::callback_lease4_; +bool HookAllocEngine4Test::callback_fake_allocation_; +vector<string> HookAllocEngine4Test::callback_argument_names_; +Pkt4Ptr HookAllocEngine4Test::callback_qry_pkt4_; +bool HookAllocEngine4Test::callback_qry_options_copy_; + +unsigned HookAllocEngine4Test::callback_skip_; + +// This test checks if the lease4_select callout is executed and expected +// parameters as passed. +TEST_F(HookAllocEngine4Test, lease4_select) { + + // Note: The following order is working as expected: + // 1. create AllocEngine (that register hook points) + // 2. call loadLibraries() + // + // This order, however, causes segfault in HooksManager + // 1. call loadLibraries() + // 2. create AllocEngine (that register hook points) + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, + 100, false))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install lease4_select + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease4_select", lease4_select_callout)); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + + AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", false); + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + ctx.callout_handle_ = callout_handle; + + Lease4Ptr lease = engine->allocateLease4(ctx); + + // Check that we got a lease + ASSERT_TRUE(lease); + + // Do all checks on the lease + checkLease4(lease); + + // Check that the lease is indeed in LeaseMgr + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + + // Check that callouts were indeed called + EXPECT_EQ("lease4_select", callback_name_); + + // Check that query4 argument was set correctly + ASSERT_TRUE(callback_qry_pkt4_); + EXPECT_EQ(callback_qry_pkt4_.get(), ctx.query_.get()); + + // Now check that the lease in LeaseMgr has the same parameters + ASSERT_TRUE(callback_lease4_); + detailCompareLease(callback_lease4_, from_mgr); + + ASSERT_TRUE(callback_subnet4_); + EXPECT_EQ(subnet_->toText(), callback_subnet4_->toText()); + + EXPECT_EQ(callback_fake_allocation_, false); + + // Check if all expected parameters are reported. The order needs to be + // alphabetical to match the order returned by + // CallbackHandle::getArgumentNames() + vector<string> expected_argument_names; + expected_argument_names.push_back("fake_allocation"); + expected_argument_names.push_back("lease4"); + expected_argument_names.push_back("query4"); + expected_argument_names.push_back("subnet4"); + EXPECT_TRUE(callback_argument_names_ == expected_argument_names); + + EXPECT_TRUE(callback_qry_options_copy_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +// This test checks if lease4_select callout is able to override the values +// in a lease4. +TEST_F(HookAllocEngine4Test, change_lease4_select) { + + // Make sure that the overridden values are different than the ones from + // subnet originally used to create the lease + ASSERT_NE(valid_override_, subnet_->getValid()); + ASSERT_FALSE(subnet_->inRange(addr_override_)); + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, + 100, false))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install a callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease4_select", lease4_select_different_callout)); + + // Normally, dhcpv4_srv would passed the handle when calling allocateLease4, + // but in tests we need to create it on our own. + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + + + AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), + false, true, "somehost.example.com.", false); + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + ctx.callout_handle_ = callout_handle; + + // Call allocateLease4. Callouts should be triggered here. + Lease4Ptr lease = engine->allocateLease4(ctx); + + // Check that we got a lease + ASSERT_TRUE(lease); + + // See if the values overridden by callout are there + EXPECT_TRUE(lease->addr_.equals(addr_override_)); + EXPECT_EQ(valid_override_, lease->valid_lft_); + + // Now check if the lease is in the database + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + + // Check if values in the database are overridden + EXPECT_TRUE(from_mgr->addr_.equals(addr_override_)); + EXPECT_EQ(valid_override_, from_mgr->valid_lft_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +// This test checks if lease4_select callout can set the status to next +// step skip without the engine to retry. +TEST_F(HookAllocEngine4Test, skip_lease4_select) { + + // Create allocation engine (hook names are registered in its ctor) + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, + 100, false))); + ASSERT_TRUE(engine); + + // Initialize Hooks Manager + HookLibsCollection libraries; // no libraries at this time + ASSERT_NO_THROW(HooksManager::loadLibraries(libraries)); + + // Install a callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "lease4_select", lease4_select_skip_callout)); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + + AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", false); + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + ctx.callout_handle_ = callout_handle; + + Lease4Ptr lease = engine->allocateLease4(ctx); + + // Check that we got no lease + EXPECT_FALSE(lease); + + // Check no retry was attempted + EXPECT_EQ(1, callback_skip_); + + // Check if the callout handle state was reset after the callout. + checkCalloutHandleReset(ctx.query_); +} + +} // namespace test +} // namespace dhcp +} // namespace isc |