]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3315] use internal IOService for hooks
authorRazvan Becheriu <razvan@isc.org>
Wed, 27 Mar 2024 21:38:26 +0000 (23:38 +0200)
committerRazvan Becheriu <razvan@isc.org>
Mon, 22 Apr 2024 19:59:07 +0000 (22:59 +0300)
58 files changed:
src/bin/agent/ca_process.cc
src/bin/d2/check_exists_add.cc
src/bin/d2/check_exists_remove.cc
src/bin/d2/d2_hooks.dox
src/bin/d2/d2_process.cc
src/bin/d2/nc_remove.cc
src/bin/d2/simple_add.cc
src/bin/d2/simple_add_without_dhcid.cc
src/bin/d2/simple_remove.cc
src/bin/d2/simple_remove_without_dhcid.cc
src/bin/d2/tests/check_exists_remove_unittests.cc
src/bin/d2/tests/d2_simple_parser_unittest.cc
src/bin/d2/tests/nc_remove_unittests.cc
src/bin/d2/tests/simple_remove_unittests.cc
src/bin/d2/tests/simple_remove_without_dhcid_unittests.cc
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/dhcp4_hooks.dox
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/callout_library_4.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/dhcp6_hooks.dox
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/callout_library_4.cc
src/bin/netconf/netconf_process.cc
src/hooks/dhcp/bootp/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/flex_option/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/high_availability/ha.dox
src/hooks/dhcp/high_availability/ha_callouts.cc
src/hooks/dhcp/high_availability/ha_impl.cc
src/hooks/dhcp/high_availability/ha_impl.h
src/hooks/dhcp/high_availability/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc
src/hooks/dhcp/lease_cmds/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/mysql_cb/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/mysql_cb/mysql_cb_callouts.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
src/hooks/dhcp/perfmon/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/pgsql_cb/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_callouts.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h
src/hooks/dhcp/run_script/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/run_script/run_script.cc
src/hooks/dhcp/run_script/run_script.h
src/hooks/dhcp/run_script/run_script_callouts.cc
src/hooks/dhcp/stat_cmds/libloadtests/load_unload_unittests.cc
src/lib/asiolink/io_service.cc
src/lib/asiolink/io_service.h
src/lib/asiolink/tests/io_service_unittest.cc
src/lib/d2srv/tests/nc_trans_unittests.cc
src/lib/d2srv/testutils/nc_test_utils.cc
src/lib/dhcpsrv/testutils/Makefile.am
src/lib/dhcpsrv/testutils/lib_load_test_fixture.h [moved from src/lib/testutils/lib_load_test_fixture.h with 99% similarity]
src/lib/hooks/hooks_user.dox
src/lib/process/d_controller.cc
src/lib/testutils/Makefile.am
tools/check-lib-dependencies.sh [new file with mode: 0755]

index 9559d84444dc51e569898e9b4ec3090f70507492..536c1fe29f9b8285171dd9edeab75f5124755aeb 100644 (file)
@@ -89,6 +89,7 @@ CtrlAgentProcess::run() {
 
 size_t
 CtrlAgentProcess::runIO() {
+    getIOService()->pollExternalIOServices();
     size_t cnt = getIOService()->poll();
     if (!cnt) {
         cnt = getIOService()->runOne();
@@ -192,6 +193,17 @@ CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
 
     int rcode = 0;
     config::parseAnswer(rcode, answer);
+
+    /// Let postponed hook initializations to run.
+    try {
+        getIOService()->pollExternalIOServices();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "Error initializing hooks: "
+            << ex.what();
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
+    }
+
     return (answer);
 }
 
index 5cd46016649dbae677dc12bfaae88832a625a702..528f9863f75f76f20fc1883725f5bff6c06055a8 100644 (file)
@@ -29,10 +29,10 @@ const int CheckExistsAddTransaction::FQDN_NOT_IN_USE_EVT;
 
 CheckExistsAddTransaction::
 CheckExistsAddTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                          dhcp_ddns::NameChangeRequestPtr& ncr,
+                          DdnsDomainPtr& forward_domain,
+                          DdnsDomainPtr& reverse_domain,
+                          D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
index f55680a573c7282425229d9fa31efcae1f9ac47d..7f2f2e2e2638b6ae2f7184a40738f663087ee22c 100644 (file)
@@ -26,10 +26,10 @@ const int CheckExistsRemoveTransaction::REMOVING_REV_PTRS_ST;
 
 CheckExistsRemoveTransaction::
 CheckExistsRemoveTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                             dhcp_ddns::NameChangeRequestPtr& ncr,
+                             DdnsDomainPtr& forward_domain,
+                             DdnsDomainPtr& reverse_domain,
+                             D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
index 0c33820b8f8780b6c54951eca5ee929507745f21..55410809b7d169c784584001a13c3b6e2ff786a3 100644 (file)
@@ -55,12 +55,15 @@ to the end of this list.
 
  - @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 Kea DHCP DDNS's main loop. The hook library should hold
-   the provided pointer until the library is unloaded. The D2CfgContext
-   object provides access to the D2 running configuration.
+   structures to the hook library.
+   If the library uses any IO operations, it should create a local IOService
+   object and register it to the main IOService which is also provided. This way
+   the local IOService is used by the server to run asynchronous operations. The
+   hooks library can use the local IOService object to schedule asynchronous
+   tasks which are triggered by the D2 server's main loop. The hook library
+   should hold the provided pointer until the library is unloaded at which stage
+   it must unregister the local IOService.
+   The D2CfgContext object provides access to the D2 running configuration.
 
  - <b>Next step status</b>: If any callout sets the status to DROP, the server
    considers the configuration is incorrect and rejects it using the error
index 134498a4576a9517cec27f971985516b74882e3f..c77430cd8237202805c0eb9a4074f47d8ff34cf5 100644 (file)
@@ -129,6 +129,7 @@ D2Process::run() {
 
 size_t
 D2Process::runIO() {
+    getIOService()->pollExternalIOServices();
     // We want to block until at least one handler is called.  We'll use
     // boost::asio::io_service directly for two reasons. First off
     // asiolink::IOService::runOne is a void and boost::asio::io_service::stopped
@@ -148,7 +149,6 @@ D2Process::runIO() {
         // service is stopped it will return immediately with a cnt of zero.
         cnt = getIOService()->runOne();
     }
-
     return (cnt);
 }
 
@@ -301,6 +301,16 @@ D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
         }
     }
 
+    /// Let postponed hook initializations to run.
+    try {
+        getIOService()->pollExternalIOServices();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "Error initializing hooks: "
+            << ex.what();
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
+    }
+
     // If we are here, configuration was valid, at least it parsed correctly
     // and therefore contained no invalid values.
     // Return the success answer from above.
index f2e86efd57885501c45dd742b921d20af9d7fd6f..bb0daee0745020047e4e8bf01c2e5b69d19b52c6 100644 (file)
@@ -26,10 +26,10 @@ const int NameRemoveTransaction::REMOVING_REV_PTRS_ST;
 
 NameRemoveTransaction::
 NameRemoveTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                      dhcp_ddns::NameChangeRequestPtr& ncr,
+                      DdnsDomainPtr& forward_domain,
+                      DdnsDomainPtr& reverse_domain,
+                      D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
index 3a577f31414fc03469e453918289ef69594adcb6..ddfc2ad8381c2dcedfb911a690bd857ece2cf29f 100644 (file)
@@ -28,10 +28,10 @@ const int SimpleAddTransaction::FQDN_NOT_IN_USE_EVT;
 
 SimpleAddTransaction::
 SimpleAddTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                     dhcp_ddns::NameChangeRequestPtr& ncr,
+                     DdnsDomainPtr& forward_domain,
+                     DdnsDomainPtr& reverse_domain,
+                     D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
index 8979f8b4f301624de87a6611f6a34cbeb49d1f25..02cc8cf233ddd018f53c31be543a0f253b88ecfd 100644 (file)
@@ -28,10 +28,10 @@ const int SimpleAddWithoutDHCIDTransaction::FQDN_NOT_IN_USE_EVT;
 
 SimpleAddWithoutDHCIDTransaction::
 SimpleAddWithoutDHCIDTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                                 dhcp_ddns::NameChangeRequestPtr& ncr,
+                                 DdnsDomainPtr& forward_domain,
+                                 DdnsDomainPtr& reverse_domain,
+                                 D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
index 92765e3a74894446c1fe261ff94632b6c861f9cb..1c165dd46204901b254b484cc3e2cbbe259ef86e 100644 (file)
@@ -25,10 +25,10 @@ const int SimpleRemoveTransaction::REMOVING_REV_PTRS_ST;
 
 SimpleRemoveTransaction::
 SimpleRemoveTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                        dhcp_ddns::NameChangeRequestPtr& ncr,
+                        DdnsDomainPtr& forward_domain,
+                        DdnsDomainPtr& reverse_domain,
+                        D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
index d96939b558e91094b06d3f54b4fbe75663f7b945..ba7d8722acb6a5875e2d7f23ae03e9ecd2e539ff 100644 (file)
@@ -25,10 +25,10 @@ const int SimpleRemoveWithoutDHCIDTransaction::REMOVING_REV_PTRS_ST;
 
 SimpleRemoveWithoutDHCIDTransaction::
 SimpleRemoveWithoutDHCIDTransaction(asiolink::IOServicePtr& io_service,
-                   dhcp_ddns::NameChangeRequestPtr& ncr,
-                   DdnsDomainPtr& forward_domain,
-                   DdnsDomainPtr& reverse_domain,
-                   D2CfgMgrPtr& cfg_mgr)
+                                    dhcp_ddns::NameChangeRequestPtr& ncr,
+                                    DdnsDomainPtr& forward_domain,
+                                    DdnsDomainPtr& reverse_domain,
+                                    D2CfgMgrPtr& cfg_mgr)
     : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
                             cfg_mgr) {
     if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
index d852860297a83cf66a73820a0ccbb72357fbf353..af42ed6fef973747a8c7874958ae6ab3648fe6d2 100644 (file)
@@ -1159,7 +1159,7 @@ TEST_F(CheckExistsRemoveTransactionTest, selectingRevServerHandler) {
         // Post a server IO error event.  This simulates an IO error occurring
         // and a need to select the new server.
         ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
-                                                SERVER_IO_ERROR_EVT))
+                                                   SERVER_IO_ERROR_EVT))
                         << " num_servers: " << num_servers
                         << " selections: " << i;
     }
index fcdbb26560e2fa0ab4daee9ac7e3af083803d338..51728cbfdcaee090c2c929d2d904b43ac10f80fc 100644 (file)
@@ -378,7 +378,7 @@ public:
     /// @return the number of default items added to the tree
     size_t setDefaults(data::ElementPtr config) {
         return (SimpleParser::setListDefaults(config, D2SimpleParser::
-                                                      TSIG_KEY_DEFAULTS));
+                                              TSIG_KEY_DEFAULTS));
     }
 
     /// @brief Attempts to parse the given element into a list of TSIGKeyInfos
@@ -422,7 +422,7 @@ public:
     /// @return the number of default items added to the tree
     virtual size_t setDefaults(data::ElementPtr config) {
         return (SimpleParser::setDefaults(config, D2SimpleParser::
-                                                  DNS_SERVER_DEFAULTS));
+                                          DNS_SERVER_DEFAULTS));
     }
 
     /// @brief Attempts to parse the given element into a DnsServerInfo
@@ -468,7 +468,7 @@ public:
     /// @return the number of default items added to the tree
     virtual size_t setDefaults(data::ElementPtr config) {
         return (SimpleParser::setListDefaults(config, D2SimpleParser::
-                                                      DNS_SERVER_DEFAULTS));
+                                              DNS_SERVER_DEFAULTS));
     }
 
     /// @brief Attempts to parse the given element into a list of DnsServerInfos
@@ -579,9 +579,8 @@ public:
         // We don't use SimpleParser::setListDefaults() as this does
         // not handle sub-lists or sub-maps
         for (auto const& domain : config->listValue()) {
-            cnt += D2SimpleParser::
-                   setDdnsDomainDefaults(domain, D2SimpleParser::
-                                                 DDNS_DOMAIN_DEFAULTS);
+            cnt += D2SimpleParser::setDdnsDomainDefaults(domain, D2SimpleParser::
+                                                         DDNS_DOMAIN_DEFAULTS);
         }
 
         return (cnt);
index df00876cf383f4e64ca3bd2b9da0fd6e8df21eb3..0371ab3978cd75e6477faf84f318371c97dca460 100644 (file)
@@ -1159,7 +1159,7 @@ TEST_F(NameRemoveTransactionTest, selectingRevServerHandler) {
         // Post a server IO error event.  This simulates an IO error occurring
         // and a need to select the new server.
         ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
-                                                SERVER_IO_ERROR_EVT))
+                                                   SERVER_IO_ERROR_EVT))
                         << " num_servers: " << num_servers
                         << " selections: " << i;
     }
index 180fb2b3e0bde07a340624b9abe971c7ac8819db..b7e33d2dcd194af1b59e67f7cea858dc37487e31 100644 (file)
@@ -717,7 +717,7 @@ TEST_F(SimpleRemoveTransactionTest, selectingRevServerHandler) {
         // Post a server IO error event.  This simulates an IO error occurring
         // and a need to select the new server.
         ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
-                                                SERVER_IO_ERROR_EVT));
+                                                   SERVER_IO_ERROR_EVT));
     }
 
     // We should have exhausted the list of servers. Processing another
index 4dbd584b6fe56a666177429d6e97b827904d8de4..9479d8178e9330c8e3908fb351294605e13495e1 100644 (file)
@@ -717,7 +717,7 @@ TEST_F(SimpleRemoveWithoutDHCIDTransactionTest, selectingRevServerHandler) {
         // Post a server IO error event.  This simulates an IO error occurring
         // and a need to select the new server.
         ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
-                                                SERVER_IO_ERROR_EVT));
+                                                   SERVER_IO_ERROR_EVT));
     }
 
     // We should have exhausted the list of servers. Processing another
index 54a8d12da21a19d48716339ad0dd0d167bc46b55..a3cc72087d9d99068301fbf0b76bc0df1466df36 100644 (file)
@@ -241,6 +241,7 @@ ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
         HookLibsCollection loaded = HooksManager::getLibraryInfo();
         HooksManager::prepareUnloadLibraries();
         static_cast<void>(HooksManager::unloadLibraries());
+        getIOService()->clearExternalIOServices();
         bool multi_threading_enabled = true;
         uint32_t thread_count = 0;
         uint32_t queue_size = 0;
@@ -452,7 +453,7 @@ ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
 
     /// Let postponed hook initializations to run.
     try {
-        getIOService()->poll();
+        getIOService()->pollExternalIOServices();
     } catch (const std::exception& ex) {
         std::ostringstream err;
         err << "Error initializing hooks: "
index ae6c903ff91a4f46907472c02a08a4ddbaab9f82..177e5600ba8b771c17b3e400860b639428bac1bc 100644 (file)
@@ -55,13 +55,17 @@ to the end of this list.
 
  - @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. The NetworkState object
-   provides access to the DHCP service state of the server and allows for
-   enabling and disabling the DHCP service from the hooks libraries.
+   structures to the hook library.
+   If the library uses any IO operations, it should create a local IOService
+   object and register it to the main IOService which is also provided. This way
+   the local IOService is used by the server to run asynchronous operations. The
+   hooks library can use the local 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 at which stage
+   it must unregister the local IOService.
+   The NetworkState object provides access to the DHCP service state of the
+   server and allows for enabling and disabling the DHCP service from the hooks
+   libraries.
 
  - <b>Next step status</b>: If any callout sets the status to DROP, the server
    will interrupt the reconfiguration process. The hook callout is expected to
index db0c632be8ea0a7fafa203e350f653e800e85a53..dd242cd0a525ea6ae88ed3896f0dccaddc8f4ce3 100644 (file)
@@ -706,6 +706,7 @@ Dhcpv4Srv::~Dhcpv4Srv() {
         }
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_UNLOAD_LIBRARIES_ERROR).arg(msg);
     }
+    getIOService()->clearExternalIOServices();
     io_service_->stop();
     io_service_->restart();
     try {
@@ -1132,6 +1133,7 @@ Dhcpv4Srv::run() {
 #endif // ENABLE_AFL
         try {
             runOne();
+            getIOService()->pollExternalIOServices();
             getIOService()->poll();
         } catch (const std::exception& e) {
             // General catch-all exception that are not caught by more specific
index f7b5869bfa3a61e8bf6cd8496475a8f452c2bdbe..e9b55d85ad7e018a2453417c67d5a418f537a929 100644 (file)
@@ -31,6 +31,9 @@ void start_service(void) {
     isc_throw(isc::Unexpected, "start service failed");
 };
 
+IOServicePtr io_service;
+IOServicePtr main_io_service;
+
 } // end anonymous
 
 // Functions accessed by the hooks framework use C linkage to avoid the name
@@ -40,6 +43,7 @@ extern "C" {
 
 int
 do_load_impl(LibraryHandle& handle) {
+    io_service.reset(new IOService());
     // Determine if this callout is configured to fail.
     isc::dhcp::SrvConfigPtr config;
     isc::data::ConstElementPtr const& parameters(handle.getParameters());
@@ -51,9 +55,17 @@ do_load_impl(LibraryHandle& handle) {
     return (0);
 }
 
+int
+do_unload_impl() {
+    if (main_io_service) {
+        main_io_service->unregisterExternalIOService(io_service);
+    }
+    return (0);
+}
+
 int (*do_load)(LibraryHandle& handle) = do_load_impl;
 
-int (*do_unload)();
+int (*do_unload)() = do_unload_impl;
 
 /// @brief Callout which appends library number and provided arguments to
 /// the marker file for dhcp4_srv_configured callout.
@@ -79,13 +91,13 @@ dhcp4_srv_configured(CalloutHandle& handle) {
 
     // Get the IO context to post start_service on it.
     std::string error("");
-    IOServicePtr io_context;
     try {
-        handle.getArgument("io_context", io_context);
-        if (!io_context) {
+        handle.getArgument("io_context", main_io_service);
+        if (!main_io_service) {
             error = "null io_context";
         }
-        io_context->post(start_service);
+        main_io_service->registerExternalIOService(io_service);
+        io_service->post(start_service);
     } catch (const std::exception& ex) {
         error = "no io_context in arguments";
     }
index 66717ed51377a48c6b2000062c8232715427d2f6..bfea49d256ae7f928ea6ef276e2a5464b83efcbc 100644 (file)
@@ -244,6 +244,7 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
         HookLibsCollection loaded = HooksManager::getLibraryInfo();
         HooksManager::prepareUnloadLibraries();
         static_cast<void>(HooksManager::unloadLibraries());
+        getIOService()->clearExternalIOServices();
         bool multi_threading_enabled = true;
         uint32_t thread_count = 0;
         uint32_t queue_size = 0;
@@ -454,7 +455,7 @@ ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
 
     /// Let postponed hook initializations to run.
     try {
-        getIOService()->poll();
+        getIOService()->pollExternalIOServices();
     } catch (const std::exception& ex) {
         std::ostringstream err;
         err << "Error initializing hooks: "
index a8bc84acb7dcef6822a41ec7ce99bf50486b8893..7c7e5dfa53c075c5014f4c4f672a56f851262f59 100644 (file)
@@ -55,14 +55,17 @@ to the end of this list.
 
  - @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. The NetworkState object
-   provides access to the DHCP service state of the server and allows for
-   enabling and disabling the DHCP service from the hooks libraries.
-
+   structures to the hook library.
+   If the library uses any IO operations, it should create a local IOService
+   object and register it to the main IOService which is also provided. This way
+   the local IOService is used by the server to run asynchronous operations. The
+   hooks library can use the local 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 at which stage
+   it must unregister the local IOService.
+   The NetworkState object provides access to the DHCP service state of the
+   server and allows for enabling and disabling the DHCP service from the hooks
+   libraries.
 
  - <b>Next step status</b>: If any callout sets the status to DROP, the server
    will interrupt the reconfiguration process. The hook callout is expected to
index a0facd7923f57768c127f7d7eba9278cd3a5d03e..0f59bc4481f58876634361455f6585720bc89926 100644 (file)
@@ -302,6 +302,7 @@ Dhcpv6Srv::~Dhcpv6Srv() {
         }
         LOG_ERROR(dhcp6_logger, DHCP6_SRV_UNLOAD_LIBRARIES_ERROR).arg(msg);
     }
+    getIOService()->clearExternalIOServices();
     io_service_->stop();
     io_service_->restart();
     try {
@@ -613,6 +614,7 @@ Dhcpv6Srv::run() {
 #endif // ENABLE_AFL
         try {
             runOne();
+            getIOService()->pollExternalIOServices();
             getIOService()->poll();
         } catch (const std::exception& e) {
             // General catch-all standard exceptions that are not caught by more
index 3036124c879f488ff481036bb0380d86fe1adcc7..0739268fc19804afed00460f3a60dc9a8d083f7b 100644 (file)
@@ -31,6 +31,9 @@ void start_service(void) {
     isc_throw(isc::Unexpected, "start service failed");
 };
 
+IOServicePtr io_service;
+IOServicePtr main_io_service;
+
 } // end anonymous
 
 // Functions accessed by the hooks framework use C linkage to avoid the name
@@ -40,6 +43,7 @@ extern "C" {
 
 int
 do_load_impl(LibraryHandle& handle) {
+    io_service.reset(new IOService());
     // Determine if this callout is configured to fail.
     isc::dhcp::SrvConfigPtr config;
     isc::data::ConstElementPtr const& parameters(handle.getParameters());
@@ -51,9 +55,17 @@ do_load_impl(LibraryHandle& handle) {
     return (0);
 }
 
+int
+do_unload_impl() {
+    if (main_io_service) {
+        main_io_service->unregisterExternalIOService(io_service);
+    }
+    return (0);
+}
+
 int (*do_load)(LibraryHandle& handle) = do_load_impl;
 
-int (*do_unload)();
+int (*do_unload)() = do_unload_impl;
 
 /// @brief Callout which appends library number and provided arguments to
 /// the marker file for dhcp6_srv_configured callout.
@@ -79,13 +91,13 @@ dhcp6_srv_configured(CalloutHandle& handle) {
 
     // Get the IO context to post start_service on it.
     std::string error("");
-    IOServicePtr io_context;
     try {
-        handle.getArgument("io_context", io_context);
-        if (!io_context) {
+        handle.getArgument("io_context", main_io_service);
+        if (!main_io_service) {
             error = "null io_context";
         }
-        io_context->post(start_service);
+        main_io_service->registerExternalIOService(io_service);
+        io_service->post(start_service);
     } catch (const std::exception& ex) {
         error = "no io_context in arguments";
     }
index c0f9b23a1688bb14a493053fa7b4e8c73590e61c..0b3dcb0a36b389b7a5708944f63c706c39ab74c1 100644 (file)
@@ -65,6 +65,7 @@ NetconfProcess::run() {
 
 size_t
 NetconfProcess::runIO() {
+    getIOService()->pollExternalIOServices();
     size_t cnt = getIOService()->poll();
     if (!cnt) {
         cnt = getIOService()->runOne();
@@ -85,6 +86,17 @@ NetconfProcess::configure(isc::data::ConstElementPtr config_set,
         getCfgMgr()->simpleParseConfig(config_set, check_only);
     int rcode = 0;
     config::parseAnswer(rcode, answer);
+
+    /// Let postponed hook initializations to run.
+    try {
+        getIOService()->pollExternalIOServices();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "Error initializing hooks: "
+            << ex.what();
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
+    }
+
     return (answer);
 }
 
index 53c7f95827299eedb82bc3dc3e8064b880e17484..79ad89ea08f3203a71cdd989cda98ec4c432753a 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index c786b868540090b7aeabacb969516c84472c3072..bffcbf83ae0e521966af609df6c160f75b382dcc 100644 (file)
 
 #include <config.h>
 
-#include <flex_option.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
-#include <testutils/lib_load_test_fixture.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index e63eefd6bde5e7d9cd3083c212f4378f316f4896..cb16c0b4a75e1d0f456fd98d39f7b97514eaa12f 100644 (file)
@@ -142,6 +142,9 @@ have been introduced. These hook points are used by the DHCPv4 and the
 DHCPv6 servers respectively, to pass the instance of the IOService
 (via "io_context" argument) to the hooks libraries which require to
 schedule asynchronous tasks.
+The hook's IOService object must be registered on the server's main IOService by
+calling registerExternalIOService and must unregister it on "unload" hook point
+by calling unregisterExternalIOService.
 
 It is also worth to note that the blocking reception of the DHCP packets
 may cause up to 1 second delays in the asynchronous operations. This is
index b0a14263014968981c49369a29f5051f2f515e5b..200efd0f6b2d3822d2464f1ba98023f6f451d366 100644 (file)
@@ -46,9 +46,8 @@ extern "C" {
 /// @param handle callout handle.
 int dhcp4_srv_configured(CalloutHandle& handle) {
     try {
-        isc::asiolink::IOServicePtr io_service;
-        handle.getArgument("io_context", io_service);
-        if (!io_service) {
+        handle.getArgument("io_context", impl->getMainIOService());
+        if (!impl->getMainIOService()) {
             // Should not happen!
             handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
             const string error("Error: io_context is null");
@@ -57,7 +56,8 @@ int dhcp4_srv_configured(CalloutHandle& handle) {
         }
         isc::dhcp::NetworkStatePtr network_state;
         handle.getArgument("network_state", network_state);
-        impl->startServices(io_service, network_state, HAServerType::DHCPv4);
+        impl->startServices(network_state, HAServerType::DHCPv4);
+        impl->getMainIOService()->registerExternalIOService(impl->getIOService());
 
     } catch (const std::exception& ex) {
         LOG_ERROR(ha_logger, HA_DHCP4_START_SERVICE_FAILED)
@@ -164,9 +164,8 @@ int lease4_server_decline(CalloutHandle& handle) {
 /// @param handle callout handle.
 int dhcp6_srv_configured(CalloutHandle& handle) {
     try {
-        isc::asiolink::IOServicePtr io_service;
-        handle.getArgument("io_context", io_service);
-        if (!io_service) {
+        handle.getArgument("io_context", impl->getMainIOService());
+        if (!impl->getMainIOService()) {
             // Should not happen!
             handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
             const string error("Error: io_context is null");
@@ -175,7 +174,8 @@ int dhcp6_srv_configured(CalloutHandle& handle) {
         }
         isc::dhcp::NetworkStatePtr network_state;
         handle.getArgument("network_state", network_state);
-        impl->startServices(io_service, network_state, HAServerType::DHCPv6);
+        impl->startServices(network_state, HAServerType::DHCPv6);
+        impl->getMainIOService()->registerExternalIOService(impl->getIOService());
 
     } catch (const std::exception& ex) {
         LOG_ERROR(ha_logger, HA_DHCP6_START_SERVICE_FAILED)
@@ -442,6 +442,17 @@ int load(LibraryHandle& handle) {
 ///
 /// @return 0 if deregistration was successful, 1 otherwise
 int unload() {
+    if (impl) {
+        if (impl->getMainIOService()) {
+            impl->getMainIOService()->unregisterExternalIOService(impl->getIOService());
+        }
+        impl->getIOService()->stop();
+        impl->getIOService()->restart();
+        try {
+            impl->getIOService()->poll();
+        } catch (...) {
+        }
+    }
     impl.reset();
     LOG_INFO(ha_logger, HA_DEINIT_OK);
     return (0);
index f0e2179328cd76463389e39bbd930ff103c6023a..127ceb9545c37ca0f5f5e1d785f86b3a0188811b 100644 (file)
@@ -31,7 +31,7 @@ namespace isc {
 namespace ha {
 
 HAImpl::HAImpl()
-    : config_(), services_(new HAServiceMapper()) {
+    : io_service_(new IOService()), config_(), services_(new HAServiceMapper()) {
 }
 
 void
@@ -40,13 +40,12 @@ HAImpl::configure(const ConstElementPtr& input_config) {
 }
 
 void
-HAImpl::startServices(const IOServicePtr& io_service,
-                      const NetworkStatePtr& network_state,
+HAImpl::startServices(const NetworkStatePtr& network_state,
                       const HAServerType& server_type) {
     auto configs = config_->getAll();
     for (auto id = 0; id < configs.size(); ++id) {
         // Create the HA service and crank up the state machine.
-        auto service = boost::make_shared<HAService>(id, io_service, network_state,
+        auto service = boost::make_shared<HAService>(id, io_service_, network_state,
                                                      configs[id], server_type);
         for (auto const& peer_config : configs[id]->getAllServersConfig()) {
             services_->map(peer_config.first, service);
@@ -54,7 +53,7 @@ HAImpl::startServices(const IOServicePtr& io_service,
     }
     // Schedule a start of the services. This ensures we begin after
     // the dust has settled and Kea MT mode has been firmly established.
-    io_service->post([&]() {
+    io_service_->post([&]() {
         for (auto const& service : services_->getAll()) {
             service->startClientAndListener();
         }
index 00f8a901fd9a035ae85eaf549813eaa4c6abdf5e..041ade8c828a5eeae1ded686dbf1f89584cd6e84 100644 (file)
@@ -45,13 +45,11 @@ public:
     /// The caller must ensure that the HA configuration is valid before
     /// calling this function.
     ///
-    /// @param io_service IO service object provided by the DHCP server.
     /// @param network_state pointer to the object holding a state of the
     /// DHCP service (enabled/disabled).
     /// @param server_type DHCP server type for which the HA service should
     /// be created.
-    void startServices(const asiolink::IOServicePtr& io_service,
-                       const dhcp::NetworkStatePtr& network_state,
+    void startServices(const dhcp::NetworkStatePtr& network_state,
                        const HAServerType& server_type);
 
     /// @brief Destructor.
@@ -223,8 +221,42 @@ public:
     HAServicePtr getHAServiceByServerName(const std::string& command_name,
                                           data::ConstElementPtr args) const;
 
+    /// @brief Get the hook I/O service.
+    ///
+    /// @return the hook I/O service.
+    isc::asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Set the hook I/O service.
+    ///
+    /// @param io_service the hook I/O service.
+    void setIOService(isc::asiolink::IOServicePtr io_service) {
+        io_service_ = io_service;
+    }
+
+    /// @brief Get the main I/O service.
+    ///
+    /// @return the main I/O service.
+    isc::asiolink::IOServicePtr& getMainIOService() {
+        return (main_io_service_);
+    }
+
+    /// @brief Set the main I/O service.
+    ///
+    /// @param io_service the main I/O service.
+    void setMainIOService(isc::asiolink::IOServicePtr io_service) {
+        main_io_service_ = io_service;
+    }
+
 protected:
 
+    /// @brief The hook I/O service.
+    isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief The main I/O service.
+    isc::asiolink::IOServicePtr main_io_service_;
+
     /// @brief Holds parsed configuration.
     HAConfigMapperPtr config_;
 
index 00197a28b3402185357f17c50486712d15a1e316..b217c5cd3feeb78a8f9a7ec25ea0a313d5c791cb 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
-#include <testutils/lib_load_test_fixture.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index e20745046514b9f9d1ada85eda6d52efec395f15..122de668dfffd255e02a6cb9e5cbf0112721f0b5 100644 (file)
@@ -127,11 +127,12 @@ public:
                                 const std::string& expected_response) {
         io_service_.reset(new IOService());
         ha_impl_.reset(new HAImpl());
+        ha_impl_->setIOService(io_service_);
         ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
         // Starting the service is required prior to running any callouts.
         NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-        ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+        ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                                 HAServerType::DHCPv4));
 
         ConstElementPtr command = Element::fromJSON(ha_sync_command);
@@ -174,13 +175,14 @@ public:
 TEST_F(HAImplTest, startServices) {
     // Valid configuration must be provided prior to starting the service.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
 
     // Network state is also required.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
 
     // Start the service for DHCPv4 server.
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // Make sure that the HA service has been created for the requested
@@ -193,13 +195,14 @@ TEST_F(HAImplTest, startServices) {
 TEST_F(HAImplTest, startServices6) {
     // Valid configuration must be provided prior to starting the service.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
 
     // Network state is also required.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
 
     // Start the service for DHCPv4 server.
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // Make sure that the HA service has been created for the requested
@@ -216,11 +219,12 @@ TEST_F(HAImplTest, buffer4Receive) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // Initially the HA service is in the waiting state and serves no scopes.
@@ -329,11 +333,12 @@ TEST_F(HAImplTest, subnet4Select) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -386,11 +391,12 @@ TEST_F(HAImplTest, subnet4SelectSharedNetwork) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -446,11 +452,12 @@ TEST_F(HAImplTest, subnet4SelectSingleRelationship) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // Create callout handle to be used for passing arguments to the
@@ -487,11 +494,12 @@ TEST_F(HAImplTest, subnet4SelectDropNoServerName) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -532,11 +540,12 @@ TEST_F(HAImplTest, subnet4SelectDropInvalidServerNameType) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -581,11 +590,12 @@ TEST_F(HAImplTest, subnet4SelectDropNotInScope) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // This server serves server1/server2 scopes but not server3/server4 scopes.
@@ -633,11 +643,12 @@ TEST_F(HAImplTest, subnet4SelectNoSubnet) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     test_ha_impl_->services_->get("server2")->serveFailoverScopes();
@@ -679,11 +690,12 @@ TEST_F(HAImplTest, buffer6Receive) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // Initially the HA service is in the waiting state and serves no scopes.
@@ -769,11 +781,12 @@ TEST_F(HAImplTest, subnet6Select) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -823,11 +836,12 @@ TEST_F(HAImplTest, subnet6SelectSharedNetwork) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -883,11 +897,12 @@ TEST_F(HAImplTest, subnet6SelectSingleRelationship) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // Create callout handle to be used for passing arguments to the
@@ -924,11 +939,12 @@ TEST_F(HAImplTest, subnet6SelectDropNoServerName) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -969,11 +985,12 @@ TEST_F(HAImplTest, subnet6SelectDropInvalidServerNameType) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // The hub is a standby server and by default serves no scopes. Explicitly
@@ -1018,11 +1035,12 @@ TEST_F(HAImplTest, subnet6SelectDropNotInScope) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // This server serves server1/server2 scopes but not server3/server4 scopes.
@@ -1070,11 +1088,12 @@ TEST_F(HAImplTest, subnet6SelectNoSubnet) {
 
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
 
     // Starting the service is required before any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     test_ha_impl_->services_->get("server2")->serveFailoverScopes();
@@ -1112,11 +1131,12 @@ TEST_F(HAImplTest, subnet6SelectNoSubnet) {
 TEST_F(HAImplTest, leases4Committed) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // Make sure we wait for the acks from the backup server to be able to
@@ -1198,11 +1218,12 @@ TEST_F(HAImplTest, leases4Committed) {
 TEST_F(HAImplTest, leases4CommittedMultipleRelationships) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1254,11 +1275,12 @@ TEST_F(HAImplTest, leases4CommittedMultipleRelationships) {
 TEST_F(HAImplTest, leases4CommittedMultipleRelationshipsNoServerName) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1306,11 +1328,12 @@ TEST_F(HAImplTest, leases4CommittedMultipleRelationshipsNoServerName) {
 TEST_F(HAImplTest, leases4CommittedMultipleRelationshipsInvalidServerName) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1360,11 +1383,12 @@ TEST_F(HAImplTest, leases4CommittedMultipleRelationshipsInvalidServerName) {
 TEST_F(HAImplTest, leases6Committed) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // Make sure we wait for the acks from the backup server to be able to
@@ -1445,11 +1469,12 @@ TEST_F(HAImplTest, leases6Committed) {
 TEST_F(HAImplTest, leases6CommittedMultipleRelationships) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1500,11 +1525,12 @@ TEST_F(HAImplTest, leases6CommittedMultipleRelationships) {
 TEST_F(HAImplTest, leases6CommittedMultipleRelationshipsNoServerName) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1551,11 +1577,12 @@ TEST_F(HAImplTest, leases6CommittedMultipleRelationshipsNoServerName) {
 TEST_F(HAImplTest, leases6CommittedMultipleRelationshipsInvalidServerName) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required before running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv6));
 
     // By enabling this setting we ensure that the lease updates are always
@@ -1682,11 +1709,12 @@ TEST_F(HAImplTest, synchronizeHandler) {
 // Tests ha-continue command handler with a specified server name.
 TEST_F(HAImplTest, continueHandler) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON("{"
@@ -1711,11 +1739,12 @@ TEST_F(HAImplTest, continueHandler) {
 // Tests ha-continue command handler without a server name.
 TEST_F(HAImplTest, continueHandlerWithNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON("{ \"command\": \"ha-continue\" }");
@@ -1735,11 +1764,12 @@ TEST_F(HAImplTest, continueHandlerWithNoServerName) {
 // Tests ha-continue command handler with wrong server name.
 TEST_F(HAImplTest, continueHandlerWithWrongServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON("{"
@@ -1764,11 +1794,12 @@ TEST_F(HAImplTest, continueHandlerWithWrongServerName) {
 // Tests status-get command processed handler.
 TEST_F(HAImplTest, statusGet) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     std::string name = "status-get";
@@ -1825,12 +1856,13 @@ TEST_F(HAImplTest, statusGet) {
 // Tests status-get command processed handler for backup server.
 TEST_F(HAImplTest, statusGetBackupServer) {
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
     test_ha_impl_->config_->get()->setThisServerName("server3");
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     std::string name = "status-get";
@@ -1875,11 +1907,12 @@ TEST_F(HAImplTest, statusGetBackupServer) {
 // passive-backup state.
 TEST_F(HAImplTest, statusGetPassiveBackup) {
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidPassiveBackupJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     std::string name = "status-get";
@@ -1924,11 +1957,12 @@ TEST_F(HAImplTest, statusGetPassiveBackup) {
 // hub-and-spoke mode.
 TEST_F(HAImplTest, statusGetHubAndSpoke) {
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidHubJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     std::string name = "status-get";
@@ -2009,11 +2043,12 @@ TEST_F(HAImplTest, statusGetHubAndSpoke) {
 // Test ha-maintenance-notify command handler with server name.
 TEST_F(HAImplTest, maintenanceNotify) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2041,11 +2076,12 @@ TEST_F(HAImplTest, maintenanceNotify) {
 // Test ha-maintenance-notify command handler without server name.
 TEST_F(HAImplTest, maintenanceNotifyNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2072,11 +2108,12 @@ TEST_F(HAImplTest, maintenanceNotifyNoServerName) {
 // Test ha-maintenance-notify command handler without server name.
 TEST_F(HAImplTest, maintenanceNotifyBadServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2104,11 +2141,12 @@ TEST_F(HAImplTest, maintenanceNotifyBadServerName) {
 // Test ha-reset command handler with a specified server name.
 TEST_F(HAImplTest, haReset) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2135,11 +2173,12 @@ TEST_F(HAImplTest, haReset) {
 // Test ha-reset command handler without a specified server name.
 TEST_F(HAImplTest, haResetNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2163,11 +2202,12 @@ TEST_F(HAImplTest, haResetNoServerName) {
 // Test ha-reset command handler with a wrong server name.
 TEST_F(HAImplTest, haResetBadServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2194,11 +2234,12 @@ TEST_F(HAImplTest, haResetBadServerName) {
 // Test ha-heartbeat command handler with a specified server name.
 TEST_F(HAImplTest, haHeartbeat) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2225,11 +2266,12 @@ TEST_F(HAImplTest, haHeartbeat) {
 // Test ha-heartbeat command handler without a specified server name.
 TEST_F(HAImplTest, haHeartbeatNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2253,11 +2295,12 @@ TEST_F(HAImplTest, haHeartbeatNoServerName) {
 // Test ha-heartbeat command handler with a wrong server name.
 TEST_F(HAImplTest, haHeartbeatBadServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2284,11 +2327,12 @@ TEST_F(HAImplTest, haHeartbeatBadServerName) {
 // Test ha-sync-complete-notify command handler with a specified server name.
 TEST_F(HAImplTest, haSyncCompleteNotify) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2316,11 +2360,12 @@ TEST_F(HAImplTest, haSyncCompleteNotify) {
 // Test ha-sync-complete-notify command handler without a specified server name.
 TEST_F(HAImplTest, haSyncCompleteNotifyNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2345,11 +2390,12 @@ TEST_F(HAImplTest, haSyncCompleteNotifyNoServerName) {
 // Test ha-sync-complete-notify command handler with a wrong server name.
 TEST_F(HAImplTest, haSyncCompleteNotifyBadServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2376,11 +2422,12 @@ TEST_F(HAImplTest, haSyncCompleteNotifyBadServerName) {
 // Test ha-scopes command handler with a specified server name.
 TEST_F(HAImplTest, haScopes) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2408,11 +2455,12 @@ TEST_F(HAImplTest, haScopes) {
 // Test ha-scopes command handler without a specified server name.
 TEST_F(HAImplTest, haScopesNoServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2439,11 +2487,12 @@ TEST_F(HAImplTest, haScopesNoServerName) {
 // Test ha-scopes command handler with a wrong server name.
 TEST_F(HAImplTest, haScopesBadServerName) {
     ha_impl_.reset(new HAImpl());
+    ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(ha_impl_->startServices(network_state,
                                             HAServerType::DHCPv4));
 
     ConstElementPtr command = Element::fromJSON(
@@ -2472,11 +2521,12 @@ TEST_F(HAImplTest, haScopesBadServerName) {
 TEST_F(HAImplTest, lease4ServerDecline) {
     // Create implementation object and configure it.
     test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
     ASSERT_NO_THROW(test_ha_impl_->configure(createValidJsonConfiguration()));
 
     // Starting the service is required prior to running any callouts.
     NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
-    ASSERT_NO_THROW(test_ha_impl_->startServices(io_service_, network_state,
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
                                                  HAServerType::DHCPv4));
 
     // Make sure we wait for the acks from the backup server to be able to
index 926dcf43978c728acc6098722f0a7b38187358f2..0d0bab6c584e30f9b54990373a81582f4d302414 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index b8b73589dbf9da971d1f164acb72b48435f3c3dd..8b27e5153dedb33fedbf4293d0836e9039b72508 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index c047f421f043ea73d8fa6214f42195a00ac69611..2186dbf31202c4bce8a7a370bcaa0daea3366114 100644 (file)
@@ -22,6 +22,7 @@
 #include <sstream>
 #include <string>
 
+using namespace isc::asiolink;
 using namespace isc::cb;
 using namespace isc::dhcp;
 using namespace isc::hooks;
@@ -67,15 +68,15 @@ int load(LibraryHandle& /* handle */) {
 /// @param handle callout handle passed to the callout.
 /// @return 0 on success, 1 otherwise.
 int dhcp4_srv_configured(CalloutHandle& handle) {
-    isc::asiolink::IOServicePtr io_service;
-    handle.getArgument("io_context", io_service);
-    if (!io_service) {
+    handle.getArgument("io_context", isc::dhcp::MySqlConfigBackendImpl::getMainIOService());
+    if (!isc::dhcp::MySqlConfigBackendImpl::getMainIOService()) {
         const string error("Error: io_context is null");
         handle.setArgument("error", error);
         handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
         return (1);
     }
-    isc::dhcp::MySqlConfigBackendImpl::setIOService(io_service);
+    isc::dhcp::MySqlConfigBackendImpl::getIOService().reset(new IOService());
+    isc::dhcp::MySqlConfigBackendImpl::getMainIOService()->registerExternalIOService(isc::dhcp::MySqlConfigBackendImpl::getIOService());
     return (0);
 }
 
@@ -86,15 +87,15 @@ int dhcp4_srv_configured(CalloutHandle& handle) {
 /// @param handle callout handle passed to the callout.
 /// @return 0 on success, 1 otherwise.
 int dhcp6_srv_configured(CalloutHandle& handle) {
-    isc::asiolink::IOServicePtr io_service;
-    handle.getArgument("io_context", io_service);
-    if (!io_service) {
+    handle.getArgument("io_context", isc::dhcp::MySqlConfigBackendImpl::getMainIOService());
+    if (!isc::dhcp::MySqlConfigBackendImpl::getMainIOService()) {
         const string error("Error: io_context is null");
         handle.setArgument("error", error);
         handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
         return (1);
     }
-    isc::dhcp::MySqlConfigBackendImpl::setIOService(io_service);
+    isc::dhcp::MySqlConfigBackendImpl::getIOService().reset(new IOService());
+    isc::dhcp::MySqlConfigBackendImpl::getMainIOService()->registerExternalIOService(isc::dhcp::MySqlConfigBackendImpl::getIOService());
     return (0);
 }
 
@@ -106,6 +107,17 @@ int unload() {
     // Unregister the factories and remove MySQL backends
     isc::dhcp::MySqlConfigBackendDHCPv4::unregisterBackendType();
     isc::dhcp::MySqlConfigBackendDHCPv6::unregisterBackendType();
+    if (isc::dhcp::MySqlConfigBackendImpl::getMainIOService()) {
+        isc::dhcp::MySqlConfigBackendImpl::getMainIOService()->unregisterExternalIOService(isc::dhcp::MySqlConfigBackendImpl::getIOService());
+    }
+    if (isc::dhcp::MySqlConfigBackendImpl::getIOService()) {
+        isc::dhcp::MySqlConfigBackendImpl::getIOService()->stop();
+        isc::dhcp::MySqlConfigBackendImpl::getIOService()->restart();
+        try {
+            isc::dhcp::MySqlConfigBackendImpl::getIOService()->poll();
+        } catch (...) {
+        }
+    }
     return (0);
 }
 
index 9e8648a4310fc4534b653453a85aa9f5f8095ba1..390bf9cd2f7b7c9faaca79139edaed4866f6aaf9 100644 (file)
@@ -29,7 +29,8 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-isc::asiolink::IOServicePtr MySqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr();
+isc::asiolink::IOServicePtr MySqlConfigBackendImpl::io_service_;
+isc::asiolink::IOServicePtr MySqlConfigBackendImpl::main_io_service_;
 
 MySqlConfigBackendImpl::
 ScopedAuditRevision::ScopedAuditRevision(MySqlConfigBackendImpl* impl,
@@ -86,6 +87,11 @@ MySqlConfigBackendImpl(const std::string& space,
     }
 }
 
+MySqlConfigBackendImpl::~MySqlConfigBackendImpl() {
+    /// nothing to do there. The conn_ connection will be deleted and its dtor
+    /// will take care of releasing the compiled statements and similar.
+}
+
 MySqlBindingPtr
 MySqlConfigBackendImpl::createBinding(const Triplet<uint32_t>& triplet) {
     if (triplet.unspecified()) {
index dcf3d29d1fc7783ac654d373cb6247960f1b4385..9931c8a551f1cc182ee311c5b148b53c360e01d7 100644 (file)
@@ -115,7 +115,7 @@ public:
                                     const db::DbCallback db_reconnect_callback);
 
     /// @brief Destructor.
-    virtual ~MySqlConfigBackendImpl() {};
+    virtual ~MySqlConfigBackendImpl();
 
     /// @brief Creates MySQL binding from an @c Optional of integer type.
     ///
@@ -836,16 +836,32 @@ public:
         return (parameters_);
     }
 
-    /// @brief Sets IO service to be used by the MySQL config backend.
+    /// @brief Get the hook I/O service.
     ///
-    /// @param IOService object, used for all ASIO operations.
-    static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
+    /// @return the hook I/O service.
+    static isc::asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Set the hook I/O service.
+    ///
+    /// @param io_service the hook I/O service.
+    static void setIOService(isc::asiolink::IOServicePtr io_service) {
         io_service_ = io_service;
     }
 
-    /// @brief Returns pointer to the IO service.
-    static isc::asiolink::IOServicePtr& getIOService() {
-        return (io_service_);
+    /// @brief Get the main I/O service.
+    ///
+    /// @return the main I/O service.
+    static isc::asiolink::IOServicePtr& getMainIOService() {
+        return (main_io_service_);
+    }
+
+    /// @brief Set the main I/O service.
+    ///
+    /// @param io_service the main I/O service.
+    static void setMainIOService(isc::asiolink::IOServicePtr io_service) {
+        main_io_service_ = io_service;
     }
 
     /// @brief Represents connection to the MySQL database.
@@ -864,8 +880,11 @@ private:
     /// @brief Connection parameters
     isc::db::DatabaseConnection::ParameterMap parameters_;
 
-    /// @brief The IOService object, used for all ASIO operations.
+    /// @brief The hook I/O service.
     static isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief The main I/O service.
+    static isc::asiolink::IOServicePtr main_io_service_;
 };
 
 } // end of namespace isc::dhcp
index 3a187cfe4e012289a4b2bd5ebee49bbed21db71e..56f1793650685903e26e4b26d3fc4557f92ff0d3 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index 71d5adb330c76628a7a2350bb2c5448538079842..11388463ce1b19a6f06aa96684567807098468ec 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index 06727e1695481f8e368f887c2616bfd32e3c6e00..f824a0c59f5e147b5705d7d7ad4d9b2bfd9b59fb 100644 (file)
@@ -22,6 +22,7 @@
 #include <sstream>
 #include <string>
 
+using namespace isc::asiolink;
 using namespace isc::cb;
 using namespace isc::dhcp;
 using namespace isc::hooks;
@@ -67,15 +68,15 @@ int load(LibraryHandle& /* handle */) {
 /// @param handle callout handle passed to the callout.
 /// @return 0 on success, 1 otherwise.
 int dhcp4_srv_configured(CalloutHandle& handle) {
-    isc::asiolink::IOServicePtr io_service;
-    handle.getArgument("io_context", io_service);
-    if (!io_service) {
+    handle.getArgument("io_context", isc::dhcp::PgSqlConfigBackendImpl::getMainIOService());
+    if (!isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()) {
         const string error("Error: io_context is null");
         handle.setArgument("error", error);
         handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
         return (1);
     }
-    isc::dhcp::PgSqlConfigBackendImpl::setIOService(io_service);
+    isc::dhcp::PgSqlConfigBackendImpl::getIOService().reset(new IOService());
+    isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()->registerExternalIOService(isc::dhcp::PgSqlConfigBackendImpl::getIOService());
     return (0);
 }
 
@@ -86,15 +87,15 @@ int dhcp4_srv_configured(CalloutHandle& handle) {
 /// @param handle callout handle passed to the callout.
 /// @return 0 on success, 1 otherwise.
 int dhcp6_srv_configured(CalloutHandle& handle) {
-    isc::asiolink::IOServicePtr io_service;
-    handle.getArgument("io_context", io_service);
-    if (!io_service) {
+    handle.getArgument("io_context", isc::dhcp::PgSqlConfigBackendImpl::getMainIOService());
+    if (!isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()) {
         const string error("Error: io_context is null");
         handle.setArgument("error", error);
         handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
         return (1);
     }
-    isc::dhcp::PgSqlConfigBackendImpl::setIOService(io_service);
+    isc::dhcp::PgSqlConfigBackendImpl::getIOService().reset(new IOService());
+    isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()->registerExternalIOService(isc::dhcp::PgSqlConfigBackendImpl::getIOService());
     return (0);
 }
 
@@ -106,6 +107,17 @@ int unload() {
     // Unregister the factories and remove PostgreSQL backends
     isc::dhcp::PgSqlConfigBackendDHCPv4::unregisterBackendType();
     isc::dhcp::PgSqlConfigBackendDHCPv6::unregisterBackendType();
+    if (isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()) {
+        isc::dhcp::PgSqlConfigBackendImpl::getMainIOService()->unregisterExternalIOService(isc::dhcp::PgSqlConfigBackendImpl::getIOService());
+    }
+    if (isc::dhcp::PgSqlConfigBackendImpl::getIOService()) {
+        isc::dhcp::PgSqlConfigBackendImpl::getIOService()->stop();
+        isc::dhcp::PgSqlConfigBackendImpl::getIOService()->restart();
+        try {
+            isc::dhcp::PgSqlConfigBackendImpl::getIOService()->poll();
+        } catch (...) {
+        }
+    }
     return (0);
 }
 
index 8b3746801722d510173059d13f8c3322d3aab38e..2104e450a11fc043bd61ee1e16101be7bd8dcbfd 100644 (file)
@@ -28,7 +28,8 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr();
+isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_;
+isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::main_io_service_;
 
 PgSqlTaggedStatement&
 PgSqlConfigBackendImpl::getStatement(size_t /* index */) const {
index 0e1917d4d007fc82eb87caf7ed347450bf34410f..a4d1d93db4aa8a49b975fb91ff62a6bf1874220b 100644 (file)
@@ -800,18 +800,6 @@ public:
         return (parameters_);
     }
 
-    /// @brief Sets IO service to be used by the PostgreSQL config backend.
-    ///
-    /// @param IOService object, used for all ASIO operations.
-    static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
-        io_service_ = io_service;
-    }
-
-    /// @brief Returns pointer to the IO service.
-    static isc::asiolink::IOServicePtr& getIOService() {
-        return (io_service_);
-    }
-
     /// @brief Fetches the SQL statement for a given statement index.
     ///
     /// Derivations must override the implementation. The reference
@@ -872,6 +860,34 @@ public:
     /// @return Number of affected rows.
     uint64_t updateDeleteQuery(size_t index, const db::PsqlBindArray& in_bindings);
 
+    /// @brief Get the hook I/O service.
+    ///
+    /// @return the hook I/O service.
+    static isc::asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Set the hook I/O service.
+    ///
+    /// @param io_service the hook I/O service.
+    static void setIOService(isc::asiolink::IOServicePtr io_service) {
+        io_service_ = io_service;
+    }
+
+    /// @brief Get the main I/O service.
+    ///
+    /// @return the main I/O service.
+    static isc::asiolink::IOServicePtr& getMainIOService() {
+        return (main_io_service_);
+    }
+
+    /// @brief Set the main I/O service.
+    ///
+    /// @param io_service the main I/O service.
+    static void setMainIOService(isc::asiolink::IOServicePtr io_service) {
+        main_io_service_ = io_service;
+    }
+
     /// @brief Represents connection to the PostgreSQL database.
     db::PgSqlConnection conn_;
 
@@ -888,9 +904,12 @@ private:
     /// @brief Connection parameters
     isc::db::DatabaseConnection::ParameterMap parameters_;
 
-    /// @brief The IOService object, used for all ASIO operations.
+    /// @brief The hook I/O service.
     static isc::asiolink::IOServicePtr io_service_;
 
+    /// @brief The main I/O service.
+    static isc::asiolink::IOServicePtr main_io_service_;
+
     /// @brief Statement index of the SQL statement to use for fetching
     /// last inserted id in a given table.
     size_t last_insert_id_index_;
index 32bf1e5e254c647de45cf381d10cfe5175fd3b33..eddbbad80f193e0b0fdd046da137ea69c92fbec9 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
-#include <testutils/lib_load_test_fixture.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index e82c56897d326a2451d848a281fb031c455dc24a..54ddb4139bdd5dfe18c79aed9767b34e485d368e 100644 (file)
@@ -19,8 +19,9 @@ namespace isc {
 namespace run_script {
 
 IOServicePtr RunScriptImpl::io_service_;
+IOServicePtr RunScriptImpl::main_io_service_;
 
-RunScriptImpl::RunScriptImpl() : name_(), sync_(false) {
+RunScriptImpl::RunScriptImpl() : io_context_(new IOService()), name_(), sync_(false) {
 }
 
 void
index 8d9f63c9615287081e952a32d0121a96da8d766e..dc9f9b8a4268a69462533b97b0d3eb23b4a3ac30 100644 (file)
@@ -30,20 +30,6 @@ public:
     /// @brief Destructor.
     ~RunScriptImpl() = default;
 
-    /// @brief Sets IO service to be used by the @ref ProcessSpawn instance.
-    ///
-    /// @param io_service The IOService object, used for all ASIO operations.
-    static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
-        io_service_ = io_service;
-    }
-
-    /// @brief Gets IO service to be used by the @ref ProcessSpawn instance.
-    ///
-    /// @return The IOService object, used for all ASIO operations.
-    static isc::asiolink::IOServicePtr getIOService() {
-        return (io_service_);
-    }
-
     /// @brief Extract boolean data and append to environment.
     ///
     /// @param value The value to be exported to target script environment.
@@ -256,7 +242,53 @@ public:
     /// @brief This function parses and applies configuration parameters.
     void configure(isc::hooks::LibraryHandle& handle);
 
+    /// @brief Get the hook I/O service.
+    ///
+    /// @return the hook I/O service.
+    isc::asiolink::IOServicePtr& getIOContext() {
+        return (io_context_);
+    }
+
+    /// @brief Set the hook I/O service.
+    ///
+    /// @param io_service the hook I/O service.
+    void setIOContext(isc::asiolink::IOServicePtr io_service) {
+        io_context_ = io_service;
+    }
+
+    /// @brief Get the hook I/O service.
+    ///
+    /// @return the hook I/O service.
+    static isc::asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Set the hook I/O service.
+    ///
+    /// @param io_service the hook I/O service.
+    static void setIOService(isc::asiolink::IOServicePtr io_service) {
+        io_service_ = io_service;
+    }
+
+    /// @brief Get the main I/O service.
+    ///
+    /// @return the main I/O service.
+    static isc::asiolink::IOServicePtr& getMainIOService() {
+        return (main_io_service_);
+    }
+
+    /// @brief Set the main I/O service.
+    ///
+    /// @param io_service the main I/O service.
+    static void setMainIOService(isc::asiolink::IOServicePtr io_service) {
+        main_io_service_ = io_service;
+    }
+
 private:
+
+    /// @brief The IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_context_;
+
     /// @brief Script name.
     std::string name_;
 
@@ -267,8 +299,11 @@ private:
     /// started.
     bool sync_;
 
-    /// @brief The IOService object, used for all ASIO operations.
+    /// @brief The hook I/O service.
     static isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief The main I/O service.
+    static isc::asiolink::IOServicePtr main_io_service_;
 };
 
 /// @brief The type of shared pointers to Run Script implementations.
index 09717b5127a183d5638f57bb72f3c1f349c45728..22ee765c9a5c8aa703d40ba6ad19d354141b3445 100644 (file)
@@ -80,7 +80,18 @@ int load(LibraryHandle& handle) {
 ///
 /// @return always 0.
 int unload() {
+    if (RunScriptImpl::getMainIOService()) {
+        RunScriptImpl::getMainIOService()->unregisterExternalIOService(impl->getIOContext());
+    }
     impl.reset();
+    if (RunScriptImpl::getIOService()) {
+        RunScriptImpl::getIOService()->stop();
+        RunScriptImpl::getIOService()->restart();
+        try {
+            RunScriptImpl::getIOService()->poll();
+        } catch (...) {
+        }
+    }
     RunScriptImpl::setIOService(IOServicePtr());
     LOG_INFO(run_script_logger, RUN_SCRIPT_UNLOAD);
     return (0);
@@ -91,16 +102,16 @@ int unload() {
 /// @param handle callout handle.
 int dhcp4_srv_configured(CalloutHandle& handle) {
     try {
-        isc::asiolink::IOServicePtr io_service;
-        handle.getArgument("io_context", io_service);
-        if (!io_service) {
+        handle.getArgument("io_context", RunScriptImpl::getMainIOService());
+        if (!RunScriptImpl::getMainIOService()) {
             // Should not happen!
             handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
             const string error("Error: io_context is null");
             handle.setArgument("error", error);
             return (1);
         }
-        RunScriptImpl::setIOService(io_service);
+        RunScriptImpl::setIOService(impl->getIOContext());
+        RunScriptImpl::getMainIOService()->registerExternalIOService(impl->getIOContext());
 
     } catch (const exception& ex) {
         LOG_ERROR(run_script_logger, RUN_SCRIPT_LOAD_ERROR)
@@ -116,16 +127,16 @@ int dhcp4_srv_configured(CalloutHandle& handle) {
 /// @param handle callout handle.
 int dhcp6_srv_configured(CalloutHandle& handle) {
     try {
-        isc::asiolink::IOServicePtr io_service;
-        handle.getArgument("io_context", io_service);
-        if (!io_service) {
+        handle.getArgument("io_context", RunScriptImpl::getMainIOService());
+        if (!RunScriptImpl::getMainIOService()) {
             // Should not happen!
             handle.setStatus(isc::hooks::CalloutHandle::NEXT_STEP_DROP);
             const string error("Error: io_context is null");
             handle.setArgument("error", error);
             return (1);
         }
-        RunScriptImpl::setIOService(io_service);
+        RunScriptImpl::setIOService(impl->getIOContext());
+        RunScriptImpl::getMainIOService()->registerExternalIOService(impl->getIOContext());
 
     } catch (const exception& ex) {
         LOG_ERROR(run_script_logger, RUN_SCRIPT_LOAD_ERROR)
index caccebaada77177a8802932ab6c1ca61f7d774fb..86444ee6a912348cbf069099dc3f724e1752e8ac 100644 (file)
 
 #include <config.h>
 
-#include <cc/data.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <hooks/hooks_manager.h>
-#include <process/daemon.h>
+#include <dhcpsrv/testutils/lib_load_test_fixture.h>
 #include <testutils/gtest_utils.h>
-#include <testutils/lib_load_test_fixture.h>
 
 #include <gtest/gtest.h>
 #include <errno.h>
index 3cc6012fe28d4cb2521985f40abe71964fb64745..8c42d6855f8be79da3dcac482a27e9a78b58d634 100644 (file)
@@ -18,98 +18,102 @@ namespace isc {
 namespace asiolink {
 
 class IOServiceImpl {
+    /// @brief Constructors and Destructor.
+    ///
+    /// @note The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
 private:
     IOServiceImpl(const IOService& source);
     IOServiceImpl& operator=(const IOService& source);
 public:
-    /// \brief The constructor
+    /// @brief The constructor.
     IOServiceImpl() :
         io_service_(),
         work_(new boost::asio::io_service::work(io_service_)) {
     };
 
-    /// \brief The destructor.
-    ~IOServiceImpl() {
-    };
+    /// @brief The destructor.
+    ~IOServiceImpl() = default;
     //@}
 
-    /// \brief Start the underlying event loop.
+    /// @brief Start the underlying event loop.
     ///
     /// This method does not return control to the caller until
-    /// the \c stop() method is called via some handler.
+    /// the @ref stop() method is called via some handler.
     void run() {
         io_service_.run();
     };
 
-    /// \brief Run the underlying event loop for a single event.
+    /// @brief Run the underlying event loop for a single event.
     ///
     /// This method return control to the caller as soon as the
     /// first handler has completed.  (If no handlers are ready when
     /// it is run, it will block until one is.)
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t runOne() {
         return (static_cast<size_t>(io_service_.run_one()));
     };
 
-    /// \brief Run the underlying event loop for a ready events.
+    /// @brief Run the underlying event loop for a ready events.
     ///
     /// This method executes handlers for all ready events and returns.
     /// It will return immediately if there are no ready events.
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t poll() {
         return (static_cast<size_t>(io_service_.poll()));
     };
 
-    /// \brief Run the underlying event loop for a ready events.
+    /// @brief Run the underlying event loop for a ready events.
     ///
     /// This method executes handlers for all ready events and returns.
     /// It will return immediately if there are no ready events.
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t pollOne() {
         return (static_cast<size_t>(io_service_.poll_one()));
     };
 
-    /// \brief Stop the underlying event loop.
+    /// @brief Stop the underlying event loop.
     ///
-    /// This will return the control to the caller of the \c run() method.
+    /// This will return the control to the caller of the @ref run() method.
     void stop() {
         io_service_.stop();
     }
 
-    /// \brief Indicates if the IOService has been stopped.
+    /// @brief Indicates if the IOService has been stopped.
     ///
-    /// \return true if the IOService has been stopped, false otherwise.
+    /// @return true if the IOService has been stopped, false otherwise.
     bool stopped() const {
         return (io_service_.stopped());
     }
 
-    /// \brief Restarts the IOService in preparation for a subsequent \c run() invocation.
+    /// @brief Restarts the IOService in preparation for a subsequent @ref run() invocation.
     void restart() {
         io_service_.reset();
     }
 
-    /// \brief Removes IO service work object to let it finish running
+    /// @brief Removes IO service work object to let it finish running
     /// when all handlers have been invoked.
     void stopWork() {
         work_.reset();
     }
 
-    /// \brief Return the native \c io_service object used in this wrapper.
+    /// @brief Return the native @ref io_service object used in this wrapper.
     ///
     /// This is a short term work around to support other Kea modules
-    /// that share the same \c io_service with the authoritative server.
+    /// that share the same @ref io_service with the authoritative server.
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
     boost::asio::io_service& getInternalIOService() {
         return (io_service_);
     }
 
-    /// \brief Post a callback on the IO service
+    /// @brief Post a callback on the IO service.
     ///
-    /// \param callback The callback to be run on the IO service.
+    /// @param callback The callback to be run on the IO service.
     void post(const std::function<void ()>& callback) {
         io_service_.post(callback);
     }
@@ -175,5 +179,25 @@ IOService::post(const std::function<void ()>& callback) {
     return (io_impl_->post(callback));
 }
 
+void
+IOService::registerExternalIOService(IOServicePtr io_service) {
+    external_io_services_.push_back(io_service);
+}
+
+void
+IOService::unregisterExternalIOService(IOServicePtr io_service) {
+    auto it = std::find(external_io_services_.begin(), external_io_services_.end(), io_service);
+    if (it != external_io_services_.end()) {
+        external_io_services_.erase(it);
+    }
+}
+
+void
+IOService::pollExternalIOServices() {
+    for (auto& io_service : external_io_services_) {
+        io_service->poll();
+    }
+}
+
 }  // namespace asiolink
 }  // namespace isc
index 7da1f9e108b530aa33d0e5983c4dbabe0a9c4d47..1800478b78072e05979cb85c4fe656695b5d85e9 100644 (file)
@@ -10,6 +10,7 @@
 #include <boost/version.hpp>
 #include <boost/shared_ptr.hpp>
 #include <functional>
+#include <list>
 
 namespace boost {
 namespace asio {
@@ -26,84 +27,89 @@ namespace isc {
 namespace asiolink {
 
 class IOServiceImpl;
+class IOService;
 
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<IOService> IOServicePtr;
+
+/// @brief The @ref IOService class is a wrapper for the ASIO @ref io_service
 /// class.
-///
 class IOService {
+    /// @brief Constructors and Destructor.
     ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
+    /// @note The copy constructor and the assignment operator are
     /// intentionally defined as private, making this class non-copyable.
     //@{
 private:
     IOService(const IOService& source);
     IOService& operator=(const IOService& source);
 public:
-    /// \brief The constructor
+    /// @brief The constructor.
     IOService();
-    /// \brief The destructor.
+
+    /// @brief The destructor.
     ~IOService();
     //@}
 
-    /// \brief Start the underlying event loop.
+    /// @brief Start the underlying event loop.
     ///
     /// This method does not return control to the caller until
-    /// the \c stop() method is called via some handler.
+    /// the @ref stop() method is called via some handler.
     void run();
 
-    /// \brief Run the underlying event loop for a single event.
+    /// @brief Run the underlying event loop for a single event.
     ///
     /// This method return control to the caller as soon as the
     /// first handler has completed.  (If no handlers are ready when
     /// it is run, it will block until one is.)
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t runOne();
 
-    /// \brief Run the underlying event loop for a ready events.
+    /// @brief Run the underlying event loop for a ready events.
     ///
     /// This method executes handlers for all ready events and returns.
     /// It will return immediately if there are no ready events.
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t poll();
 
-    /// \brief Run the underlying event loop for a ready events.
+    /// @brief Run the underlying event loop for a ready events.
     ///
     /// This method executes handlers for all ready events and returns.
     /// It will return immediately if there are no ready events.
     ///
-    /// \return The number of handlers that were executed.
+    /// @return The number of handlers that were executed.
     size_t pollOne();
 
-    /// \brief Stop the underlying event loop.
+    /// @brief Stop the underlying event loop.
     ///
-    /// This will return the control to the caller of the \c run() method.
+    /// This will return the control to the caller of the @ref run() method.
     void stop();
 
-    /// \brief Indicates if the IOService has been stopped.
+    /// @brief Indicates if the IOService has been stopped.
     ///
-    /// \return true if the IOService has been stopped, false otherwise.
+    /// @return true if the IOService has been stopped, false otherwise.
     bool stopped() const;
 
-    /// \brief Restarts the IOService in preparation for a subsequent \c run() invocation.
+    /// @brief Restarts the IOService in preparation for a subsequent @ref run() invocation.
     void restart();
 
-    /// \brief Removes IO service work object to let it finish running
+    /// @brief Removes IO service work object to let it finish running
     /// when all handlers have been invoked.
     void stopWork();
 
-    /// \brief Return the native \c io_service object used in this wrapper.
+    /// @brief Return the native @ref io_service object used in this wrapper.
     ///
     /// This is a short term work around to support other Kea modules
-    /// that share the same \c io_service with the authoritative server.
+    /// that share the same @ref io_service with the authoritative server.
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
+    ///
+    /// @return The internal io_service object.
     boost::asio::io_service& getInternalIOService();
 
-    /// \brief Post a callback to the end of the queue.
+    /// @brief Post a callback to the end of the queue.
     ///
     /// Requests the callback be called sometime later. It is not guaranteed
     /// by the underlying asio, but it can reasonably be expected the callback
@@ -114,12 +120,39 @@ public:
     /// by small bits that are called from time to time).
     void post(const std::function<void ()>& callback);
 
+    /// @brief Register external IOService.
+    ///
+    /// @param io_service The external IOService to be registered.
+    void registerExternalIOService(IOServicePtr io_service);
+
+    /// @brief Unregister external IOService.
+    ///
+    /// @param io_service The external IOService to be unregistered.
+    void unregisterExternalIOService(IOServicePtr io_service);
+
+    /// @brief Clear the list of external IOService objects.
+    void clearExternalIOServices() {
+        external_io_services_.clear();
+    }
+
+    /// @brief The count of external IOService objects.
+    ///
+    // @return The count of external IOService objects.
+    size_t externalIOServiceCount() {
+        return (external_io_services_.size());
+    }
+
+    /// @brief Poll external IOService objects.
+    void pollExternalIOServices();
+
 private:
+
+    /// @brief The implementation.
     boost::shared_ptr<IOServiceImpl> io_impl_;
-};
 
-/// @brief Defines a smart pointer to an IOService instance.
-typedef boost::shared_ptr<IOService> IOServicePtr;
+    /// @brief The list of external IOService objects.
+    std::list<IOServicePtr> external_io_services_;
+};
 
 }  // namespace asiolink
 }  // namespace isc
index 7a96702e83b48b970b1dc4509a6b8ffa6d51588a..01a550d0f95b36a66d2c487796f61e7a7925c868 100644 (file)
@@ -45,4 +45,105 @@ TEST(IOService, post) {
     EXPECT_EQ(3, called[2]);
 }
 
+TEST(IOService, externalIOService) {
+    IOServicePtr main_io_service(new IOService());
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 0);
+    int one_io_callback_count = 0;
+    auto one_f = [&one_io_callback_count] () {
+        one_io_callback_count++;
+    };
+    int two_io_callback_count = 0;
+    auto two_f = [&two_io_callback_count] () {
+        two_io_callback_count++;
+    };
+    {
+        IOServicePtr one_io_service(new IOService());
+        one_io_service->post(one_f);
+
+        IOServicePtr two_io_service(new IOService());
+        two_io_service->post(two_f);
+
+        main_io_service->pollExternalIOServices();
+        EXPECT_EQ(one_io_callback_count, 0);
+        EXPECT_EQ(two_io_callback_count, 0);
+
+        main_io_service->registerExternalIOService(one_io_service);
+        EXPECT_EQ(main_io_service->externalIOServiceCount(), 1);
+
+        main_io_service->registerExternalIOService(two_io_service);
+        EXPECT_EQ(main_io_service->externalIOServiceCount(), 2);
+
+        main_io_service->pollExternalIOServices();
+        EXPECT_EQ(one_io_callback_count, 1);
+        EXPECT_EQ(two_io_callback_count, 1);
+        one_io_service->post(one_f);
+        two_io_service->post(two_f);
+    }
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 2);
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 2);
+    EXPECT_EQ(two_io_callback_count, 2);
+
+    main_io_service->clearExternalIOServices();
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 0);
+
+    IOServicePtr one_io_service(new IOService());
+    one_io_service->post(one_f);
+
+    IOServicePtr two_io_service(new IOService());
+    two_io_service->post(two_f);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 2);
+    EXPECT_EQ(two_io_callback_count, 2);
+
+    main_io_service->registerExternalIOService(one_io_service);
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 1);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 3);
+    EXPECT_EQ(two_io_callback_count, 2);
+    one_io_service->post(one_f);
+    two_io_service->post(two_f);
+
+    main_io_service->registerExternalIOService(two_io_service);
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 2);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 4);
+    EXPECT_EQ(two_io_callback_count, 4);
+    one_io_service->post(one_f);
+    two_io_service->post(two_f);
+
+    main_io_service->unregisterExternalIOService(one_io_service);
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 1);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 4);
+    EXPECT_EQ(two_io_callback_count, 5);
+    one_io_service->post(one_f);
+    two_io_service->post(two_f);
+
+    main_io_service->unregisterExternalIOService(two_io_service);
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 0);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 4);
+    EXPECT_EQ(two_io_callback_count, 5);
+
+    EXPECT_NO_THROW(main_io_service->registerExternalIOService(main_io_service));
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 1);
+    main_io_service->post(one_f);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 5);
+    EXPECT_EQ(two_io_callback_count, 5);
+    EXPECT_NO_THROW(main_io_service->unregisterExternalIOService(main_io_service));
+    EXPECT_EQ(main_io_service->externalIOServiceCount(), 0);
+
+    main_io_service->pollExternalIOServices();
+    EXPECT_EQ(one_io_callback_count, 5);
+    EXPECT_EQ(two_io_callback_count, 5);
+}
+
 }
index 2f4a1ba9ce287704508c986ff01e0c870dde7143..39e024f39a02010c862ff97499dce762c8b1009b 100644 (file)
@@ -579,7 +579,7 @@ TEST_F(NameChangeTransactionTest, responseString) {
     EXPECT_EQ("IO_STOPPED", name_change_->responseString());
 
     ASSERT_NO_THROW(name_change_->setDnsUpdateStatus(DNSClient::
-                                                    INVALID_RESPONSE));
+                                                     INVALID_RESPONSE));
     EXPECT_EQ("INVALID_RESPONSE", name_change_->responseString());
 
     ASSERT_NO_THROW(name_change_->setDnsUpdateStatus(DNSClient::OTHER));
index c9aaf0b1d045755ee6d46b72706e732fee013b94..563b5ba69e2d7a7bf4c7a277c13b03faf399bc04 100644 (file)
@@ -540,7 +540,7 @@ checkAddFwdAddressRequest(NameChangeTransaction& tran) {
     dns::RRsetPtr rrset;
     checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::ANY(), 0, ncr);
 
     // Verify the UPDATE SECTION
@@ -552,12 +552,12 @@ checkAddFwdAddressRequest(NameChangeTransaction& tran) {
 
     // First, Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Now, verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
             ttl, ncr);
 
@@ -595,12 +595,12 @@ checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
 
     // Verify the FQDN test RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(), 0, ncr);
 
     // Verify the DHCID test RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 1));
+                                         SECTION_PREREQUISITE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), 0, ncr);
 
     // Verify the UPDATE SECTION
@@ -613,12 +613,12 @@ checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
 
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
@@ -663,25 +663,25 @@ checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
 
     // Verify the PTR delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(),
             0, ncr);
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(),
             0, ncr);
 
     // Verify the PTR add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 2));
+                                         SECTION_UPDATE, 2));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
             ttl, ncr);
 
     // Verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 3));
+                                         SECTION_UPDATE, 3));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::DHCID(),
             ttl, ncr);
 
@@ -714,7 +714,7 @@ checkRemoveFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the DHCID matching assertion RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
             0, ncr);
 
@@ -724,7 +724,7 @@ checkRemoveFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the FQDN/IP delete RR.
     const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), exp_ip_rr_type,
             0, ncr);
 
@@ -754,19 +754,19 @@ checkRemoveFwdRRsRequest(NameChangeTransaction& tran) {
     // Verify the DHCID matches assertion.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
             0, ncr);
 
     // Verify the NO A RRs assertion.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 1));
+                                         SECTION_PREREQUISITE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::A(),
             0, ncr, NO_RDATA);
 
     // Verify the NO AAAA RRs assertion.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 2));
+                                         SECTION_PREREQUISITE, 2));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(),
             0, ncr, NO_RDATA);
 
@@ -775,7 +775,7 @@ checkRemoveFwdRRsRequest(NameChangeTransaction& tran) {
 
     // Verify the delete all for the FQDN RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(),
             0, ncr);
 
@@ -805,7 +805,7 @@ checkRemoveRevPtrsRequest(NameChangeTransaction& tran) {
     // Verify the FQDN-PTRNAME assertion RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
             0, ncr);
 
@@ -814,7 +814,7 @@ checkRemoveRevPtrsRequest(NameChangeTransaction& tran) {
 
     // Verify the delete all for the FQDN RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::ANY(),
             0, ncr);
 
@@ -876,23 +876,23 @@ checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran) {
 
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
             0, ncr);
 
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 2));
+                                         SECTION_UPDATE, 2));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Now, verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 3));
+                                         SECTION_UPDATE, 3));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
             ttl, ncr);
 
@@ -929,12 +929,12 @@ checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran) {
     // Verify the FQDN delete RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
             0, ncr);
 
@@ -970,12 +970,12 @@ checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran) {
     // Verify the FQDN delete RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(), 0, ncr);
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(), 0, ncr);
 
     // Verify that it will render toWire without throwing.
@@ -1021,7 +1021,7 @@ checkExistsReplaceFwdAddressRequest(NameChangeTransaction& tran) {
 
     // Verify the DHCID test RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(), 0, ncr);
 
     // Verify the UPDATE SECTION
@@ -1037,22 +1037,22 @@ checkExistsReplaceFwdAddressRequest(NameChangeTransaction& tran) {
 
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(), 0, ncr);
 
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 2));
+                                         SECTION_UPDATE, 2));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 3));
+                                         SECTION_UPDATE, 3));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
             ttl, ncr);
 
@@ -1088,7 +1088,7 @@ checkExistsRemoveFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the DHCID exists assertion RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
             0, ncr);
 
@@ -1098,7 +1098,7 @@ checkExistsRemoveFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the FQDN/IP delete RR.
     const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), exp_ip_rr_type,
             0, ncr);
 
@@ -1131,13 +1131,13 @@ checkExistsRemoveFwdRRsRequest(NameChangeTransaction& tran) {
     // Verify the NO A RRs assertion.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 0));
+                                         SECTION_PREREQUISITE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::A(),
             0, ncr, NO_RDATA);
 
     // Verify the NO AAAA RRs assertion.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_PREREQUISITE, 1));
+                                         SECTION_PREREQUISITE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(),
             0, ncr, NO_RDATA);
 
@@ -1146,7 +1146,7 @@ checkExistsRemoveFwdRRsRequest(NameChangeTransaction& tran) {
 
     // Verify the DHCID delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
             0, ncr);
 
@@ -1190,12 +1190,12 @@ checkSimpleReplaceFwdAddressWithoutDHCIDRequest(NameChangeTransaction& tran) {
 
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
@@ -1234,7 +1234,7 @@ checkSimpleRemoveFwdRRsWithoutDHCIDRequest(NameChangeTransaction& tran) {
     // Verify the FQDN delete RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
 
     // Verify that it will render toWire without throwing.
@@ -1275,13 +1275,13 @@ checkSimpleReplaceRevPtrsWithoutDHCIDRequest(NameChangeTransaction& tran) {
 
     // Verify the PTR delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(),
             0, ncr);
 
     // Verify the PTR add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 1));
+                                         SECTION_UPDATE, 1));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
             ttl, ncr);
 
@@ -1320,7 +1320,7 @@ checkSimpleRemoveRevPtrsWithoutDHCIDRequest(NameChangeTransaction& tran) {
     // Verify the FQDN delete RR.
     dns::RRsetPtr rrset;
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
-                                                  SECTION_UPDATE, 0));
+                                         SECTION_UPDATE, 0));
     checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(), 0, ncr);
 
     // Verify that it will render toWire without throwing.
index aa4f83f8397e2ec6cb12032538441630b12a80b0..471d61deeb69ea4a7a99035ecff58a614a725628 100644 (file)
@@ -16,6 +16,7 @@ libdhcpsrvtest_la_SOURCES  = concrete_lease_mgr.cc concrete_lease_mgr.h
 libdhcpsrvtest_la_SOURCES += config_result_check.cc config_result_check.h
 libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
 libdhcpsrvtest_la_SOURCES += host_data_source_utils.cc host_data_source_utils.h
+libdhcpsrvtest_la_SOURCES += lib_load_test_fixture.h
 libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source.h
 libdhcpsrvtest_la_SOURCES += test_utils.cc test_utils.h
 libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h
similarity index 99%
rename from src/lib/testutils/lib_load_test_fixture.h
rename to src/lib/dhcpsrv/testutils/lib_load_test_fixture.h
index 918656d4fd2d9e980a34c0f3fb47dec77b059829..11f03b320146d781a2c599c8f6f62df00d5d2e83 100644 (file)
@@ -9,7 +9,9 @@
 
 #include <cc/data.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <hooks/hooks_manager.h>
 #include <process/daemon.h>
+
 #include <testutils/gtest_utils.h>
 
 namespace isc {
index ce9ff442c0233a2f9843e12d38307e1b38b5cb35..e6e96e392dba2a1b097a573c9a3cb956d4ad11ca 100644 (file)
@@ -1620,7 +1620,7 @@ cb4_updated and cb6_updated have no thread safety requirements.
 
 Other hook library entry points are called by the main thread:
  - io service (io context is recent boost versions) is polled by the main
-  thread
+   thread
  - external socket callbacks are executed by the main thread
  - commands including command_process
 
index 1b9276df8237a6936e5368b37a6eb7df158d7d01..6f2994accbca67d2e3e468ff19da8737de3f6a4f 100644 (file)
@@ -415,6 +415,8 @@ DControllerBase::configFromFile() {
         // case of problems.
         storage->applyLoggingCfg();
 
+        getIOService()->clearExternalIOServices();
+
         answer = updateConfig(module_config);
         // In all cases the right logging configuration is in the context.
         process_->getCfgMgr()->getContext()->applyLoggingCfg();
@@ -655,6 +657,8 @@ DControllerBase::configSetHandler(const std::string&, ConstElementPtr args) {
         // case of problems.
         storage->applyLoggingCfg();
 
+        getIOService()->clearExternalIOServices();
+
         ConstElementPtr answer = updateConfig(module_config);
         int rcode = 0;
         parseAnswer(rcode, answer);
@@ -844,6 +848,8 @@ DControllerBase::~DControllerBase() {
         LOG_ERROR(dctl_logger, DCTL_UNLOAD_LIBRARIES_ERROR).arg(msg);
     }
 
+    getIOService()->clearExternalIOServices();
+
     io_signal_set_.reset();
     try {
         getIOService()->poll();
index 03d78fbab169f6d8c0aeaa471e32b6c49c07df42..30332da54810bd7c83ec99291ba48c83605d7f9b 100644 (file)
@@ -16,7 +16,6 @@ libkea_testutils_la_SOURCES += unix_control_client.cc unix_control_client.h
 libkea_testutils_la_SOURCES += user_context_utils.cc user_context_utils.h
 libkea_testutils_la_SOURCES += gtest_utils.h
 libkea_testutils_la_SOURCES += multi_threading_utils.h
-libkea_testutils_la_SOURCES += lib_load_test_fixture.h
 libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libkea_testutils_la_LIBADD  = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
diff --git a/tools/check-lib-dependencies.sh b/tools/check-lib-dependencies.sh
new file mode 100755 (executable)
index 0000000..b2ae9ac
--- /dev/null
@@ -0,0 +1,319 @@
+#!/bin/bash
+
+# extract folder name containing file
+#
+# param ${1} file name
+# return folder name
+extract_folder_name() {
+       # return name of the file until last '/'
+       echo "$(echo "${1}" | rev | cut -d '/' -f 2- | rev)"
+}
+
+# extract all includes found in source files found in the same folder as specified Makefile.am
+#
+# param ${1} path to a Makefile.am
+# return all dependencies libs in the order of compilation
+extract_includes() {
+       # extract folder name from current library Makefile.am
+       CURRENT_FOLDER=$(extract_folder_name "${1}")"/"
+       # select only files in current folder
+       SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
+       # select all lines containing '#include ' directive
+       RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
+       # filter only included dependencies found in other libraries by using the form 'other_lib_name/header_file.h'
+       # to do this it is required to select the string between '<' and '>', searching for '/' character and returning the name until last '/'
+       RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "\/" | rev | cut -d "/" -f 2 | rev | sort | uniq)
+       # filter includes that are not compiled by the project's Makefiles
+       INCLUDES_LIST=
+       for i in ${LIBRARIES_LIST}; do
+               for j in ${RAW_INCLUDES_LIST}; do
+                       if test "${j}" = "${i}"; then
+                               INCLUDES_LIST="${i} ${INCLUDES_LIST}"
+                               break
+                       fi
+               done
+       done
+       # remove empty spaces
+       INCLUDES_LIST=$(echo ${INCLUDES_LIST} | tr -s " ")
+       # order dependencies in the order of compilation
+       FILTERED_INCLUDES_LIST=
+       for i in ${LIBRARIES_LIST}; do
+               if test $(echo "${INCLUDES_LIST}" | grep "\b${i}\b" | wc -l) -ne 0; then
+                       FILTERED_INCLUDES_LIST="${i} ${FILTERED_INCLUDES_LIST}"
+               fi
+       done
+       echo "${FILTERED_INCLUDES_LIST}"
+}
+
+# extract all header only files and headers and source files found in the external library required by specified library
+# param ${1} name of the current library
+# param ${2} name of the external dependency library required by current library
+# return the list of header only files as 'HEADERS: heaser1.h header2.h' and header and source files as 'HEADERS_AND_SOURCES: source1.h source1.cc source2.h source2.cpp'
+extract_non_include_files() {
+       # extract folder name for current library Makefile.am
+       CURRENT_FOLDER=$(extract_folder_name "src/lib/${1}/Makefile.am")"/"
+       # extract folder name for external dependency library Makefile.am
+       EXTERNAL_FOLDER=$(extract_folder_name "src/lib/${2}/Makefile.am")"/"
+       # select only files in current folder
+       SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
+       HEADERS_LIST=
+       NON_HEADERS_LIST=
+       # select all lines containing '#include ' directive
+       RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
+       # filter only included headers found in other libraries by using the form 'other_lib_name/header_file.h'
+       # to do this it is required to select the string between '<' and '>', searching for '/' character, search for the extension marker '.' and returning the name after last '/'
+       RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "\/" | grep "\b${2}\b" | cut -d "/" -f 2 | grep "\." | sort | uniq)
+       # select only files in dependency library folder and strip full path
+       RELATIVE_SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${EXTERNAL_FOLDER}" | sed -e "s#${REPO_FOLDER}${EXTERNAL_FOLDER}##g")
+       # search for the header file but also for source files
+       for i in ${RAW_INCLUDES_LIST}; do
+               # filter by name only (no extension)
+               FILTER=$(echo "${i}" | cut -d "." -f 1)
+               # filter non header files with exact name of the header file without the extension
+               NON_HEADER=$(echo "${RELATIVE_SEARCH_FILES}" | grep "\b${FILTER}\." | grep -v "${i}")
+               if test $(echo "${NON_HEADER}" | wc -w) -ne 0; then
+                       # append header and source file names
+                       NON_HEADERS_LIST="${i} ${NON_HEADER} ${NON_HEADERS_LIST}"
+               else
+                       # append header only file name
+                       HEADERS_LIST="${i} ${HEADERS_LIST}"
+               fi
+       done
+       # sort header only files
+       HEADERS_LIST=$(echo ${HEADERS_LIST} | tr -s " " | sort | uniq)
+       # sort header and source files
+       NON_HEADERS_LIST=$(echo ${NON_HEADERS_LIST} | tr -s " " | sort | uniq)
+       echo "HEADERS_AND_SOURCES:${NON_HEADERS_LIST}"
+       echo "HEADERS:${HEADERS_LIST}"
+}
+
+# extract all valid dependencies of a specified library
+#
+# param ${1} list of all libraries in the reverse compilation order
+# param ${2} library name for which the dependency list is computed
+# return the list of dependencies for specified library in the reverse compilation order 
+extract_dependencies() {
+       echo "${1}" | grep -Eo "\b${2}\b.*$"
+}
+
+# extract computed dependency for specified library
+#
+# param ${1} library name for which the dependency list is retrieved
+# param ${2} library path for which the dependency list is retrieved
+# return stored value of computed dependencies or 'NONE' if dependencies have not been computed yet
+extract_computed_dependencies() {
+       PATH_TO_NAME=$(echo "${2}" | tr -s "/" "_")
+       NAME="COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${1}"
+       if test -n "${!NAME+x}"; then
+               echo "${!NAME}"
+       else
+               echo "NONE"
+       fi
+}
+
+# extract library directive
+#
+# param ${1} artifact path
+extract_library_directive() {
+       ARTIFACT_PATH="${1}"
+       echo `cat ${ARTIFACT_PATH}/Makefile.am | grep "LIBADD\|LDADD" | sort | tr -s ' ' | cut -d " " -f 1 | sort -u`
+}
+
+# extract library name
+#
+# param ${1} artifact path
+extract_library_name() {
+       ARTIFACT_PATH="${1}"
+       echo `cat ${ARTIFACT_PATH}/Makefile.am | grep "LIBRARIES" | tr -s ' ' | cut -d " " -f 3`
+}
+
+# compute artifact dependencies
+#
+# param ${1} artifact name
+# param ${2} artifact path
+compute_dependencies() {
+       ARTIFACT="${1}"
+       ARTIFACT_PATH="${2}"
+       echo ""
+       echo "########################################"
+       echo "### ${ARTIFACT_PATH}/${ARTIFACT}"
+       echo "########################################"
+       echo ""
+       # all valid dependencies that can be added by each dependency library
+       echo "${ARTIFACT_PATH}/${ARTIFACT} valid dependencies:"
+       echo "${VALID_LIST}"
+       # detect dependencies errors by searching for dependencies that are compiled after the current library and can generate missing symbols
+       NON_RECURSIVE_BASE_DEPENDENCIES=
+       for j in ${BASE_DEPENDENCIES}; do
+               # only add the dependency if it is in the valid dependencies list to prevent infinite recursion and log the error otherwise
+               if test $(echo "${VALID_LIST}" | grep "\b${j}\b" | wc -l) -eq 0; then
+                       # search for external header and source files
+                       INVALID_EXTERNAL_DEPENDENCIES=$(extract_non_include_files "${ARTIFACT}" "${j}")
+                       # filter header only external files
+                       EXTERNAL_HEADERS=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS:" | cut -d ":" -f 2)
+                       # filter header and source external files
+                       EXTERNAL_ALL=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS_AND_SOURCES:" | cut -d ":" -f 2)
+                       echo "### ERROR ### dependencies ERROR for ${ARTIFACT_PATH}/${ARTIFACT} on ${j} with:"
+                       # if there are any header only external files
+                       if test $(echo "${EXTERNAL_ALL}" | wc -w) -ne 0; then
+                               echo "non header only files: ${EXTERNAL_ALL}"
+                       fi
+                       # if there are any header and source external files
+                       if test $(echo "${EXTERNAL_HEADERS}" | wc -w) -ne 0; then
+                               echo "header only files: ${EXTERNAL_HEADERS}"
+                       fi
+               else
+                       # don't add current library to it's dependencies list
+                       if test ${j} != ${ARTIFACT}; then
+                               NON_RECURSIVE_BASE_DEPENDENCIES="${NON_RECURSIVE_BASE_DEPENDENCIES} ${j}"
+                       fi
+               fi
+       done
+       # all found dependencies in the reverse compilation order
+       BASE_DEPENDENCIES=$(echo "${BASE_DEPENDENCIES}" | xargs)
+       # all found and valid dependencies in the reverse compilation order
+       NON_RECURSIVE_BASE_DEPENDENCIES=$(echo "${NON_RECURSIVE_BASE_DEPENDENCIES}" | xargs)
+       echo "${ARTIFACT_PATH}/${ARTIFACT} base dependencies:"
+       echo "${BASE_DEPENDENCIES}"
+       echo "${ARTIFACT_PATH}/${ARTIFACT} non recursive dependencies:"
+       echo "${NON_RECURSIVE_BASE_DEPENDENCIES}"
+       # minimum set of dependencies for current library
+       DEPENDENCIES=
+       for j in ${NON_RECURSIVE_BASE_DEPENDENCIES}; do
+               NEW_DEPENDENCIES=$(extract_computed_dependencies "${j}" "src/lib")
+               if test "${NEW_DEPENDENCIES}" == "NONE"; then
+                       echo "### ERROR ### computed dependency not found for ${j}"
+               else
+                       DEPENDENCIES="${NEW_DEPENDENCIES} ${DEPENDENCIES}"
+               fi
+       done
+       DEPENDENCIES=$(echo "${DEPENDENCIES} ${NON_RECURSIVE_BASE_DEPENDENCIES}" | tr -s " " "\n" | sort | uniq | xargs)
+       # order dependencies in the order of compilation
+       SORTED_DEPENDENCIES=
+       for j in ${LIBRARIES_LIST}; do
+               if test $(echo "${DEPENDENCIES}" | grep "\b${j}\b" | wc -l) -ne 0; then
+                       SORTED_DEPENDENCIES="${j} ${SORTED_DEPENDENCIES}"
+               fi
+       done
+       echo "${ARTIFACT_PATH}/${ARTIFACT} minimum dependencies:"
+       echo "${SORTED_DEPENDENCIES}"
+       echo ""
+       echo "++++++++++++++++++++++++++++++++++++++++"
+       ARTIFACT_DIRECTIVE=$(extract_library_directive ${ARTIFACT_PATH}/${ARTIFACT})
+       for j in ${SORTED_DEPENDENCIES}; do
+               DEPENDENCY_LIBRARY_NAME=$(extract_library_name "src/lib/${j}")
+               echo "${ARTIFACT_DIRECTIVE} += \$(top_builddir)/src/lib/${j}/${DEPENDENCY_LIBRARY_NAME}"
+       done
+       echo "++++++++++++++++++++++++++++++++++++++++"
+       echo "########################################"
+       echo ""
+}
+
+# if wrong number of parameters print usage
+if test ${#} -ne 1; then
+       echo "Usage: ${0} path/to/kea/repo"
+       exit
+fi
+
+# folder containing full repo
+REPO_FOLDER=${1}
+
+if test $(echo -n ${REPO_FOLDER} | tail -c 1) != "/"; then
+       REPO_FOLDER="${REPO_FOLDER}/"
+fi
+
+# filter all Makefile.am files
+MAKEFILES_LIST=$(find ${REPO_FOLDER} | grep "Makefile\.am" | sed -e "s#${REPO_FOLDER}##g" | grep "src\/" | sort)
+
+# if no Makefile.am found exit
+if test -z "${MAKEFILES_LIST}"; then
+       echo "invalid repo path: no Makefile.am file found"
+       exit
+fi
+
+echo "list of Makefile.am:"
+echo "${MAKEFILES_LIST}"
+
+# base Makefile.am for all sources is in src/lib/Makefile.am
+BASE_MAKEFILE=$(echo "${MAKEFILES_LIST}" | grep "src\/lib\/Makefile.am")
+
+# if no src/lib/Makefile.am found exit
+if test -z ${BASE_MAKEFILE}; then
+       echo "invalid repo path: no src/lib/Makefile.am file found"
+       exit
+fi
+
+echo "base Makefile.am:"
+echo "${BASE_MAKEFILE}"
+
+# generate the list of libraries in the compilation order
+LIBRARIES_LIST=
+RAW_LIBRARIES_LIST=$(cat "${REPO_FOLDER}${BASE_MAKEFILE}" | grep "SUBDIRS")
+for i in ${RAW_LIBRARIES_LIST}; do
+       LIBRARIES_LIST="${LIBRARIES_LIST} $(echo ${i} | grep -v "SUBDIRS" | grep -v '=')"
+done
+
+# remove empty spaces
+LIBRARIES_LIST=$(echo "${LIBRARIES_LIST}" | tr -s ' ' | xargs)
+
+# generate the list of libraries in the reverse compilation order
+REVERSE_LIBRARIES_LIST=
+for i in ${LIBRARIES_LIST}; do
+       REVERSE_LIBRARIES_LIST="${i} ${REVERSE_LIBRARIES_LIST}"
+done
+
+echo "list of libraries:"
+echo "${LIBRARIES_LIST}"
+
+echo "reverse list of libraries:"
+echo "${REVERSE_LIBRARIES_LIST}"
+
+# filter all files of interest ignoring irrelevant ones
+# ignore .git, .libs, .deps doc folders and .o .lo .Plo .Po .gcno .gcda .m4 .dox .json .mes files
+FILE_LIST=$(find "${REPO_FOLDER}" 2>/dev/null | grep -v "\.git" | grep -v "\/\.libs\/" | grep -v "\.o$" | grep -v "\/\.deps\/" | grep -v "\.lo$" | grep -v "\.Plo$" | grep -v "\.Po$" | grep -v "\.gcno$" | grep -v "gcda" | grep -v "\.m4$" | grep -v "\.dox$" | grep -v "\.json$" | grep -v "\/doc\/" | grep -v "\.mes$" | sort)
+
+#echo "files:"
+#echo "${FILE_LIST}"
+
+BASE_LIBRARIES_MAKEFILES=
+
+# generate the list of dependencies for all libraries in src/lib
+for i in ${LIBRARIES_LIST}; do
+       # generate current library Makefile.am path
+       BASE_LIBRARIES_MAKEFILES="${BASE_LIBRARIES_MAKEFILES} src/lib/${i}/Makefile.am"
+       # extract dependencies found in the library folder
+       BASE_DEPENDENCIES=$(extract_includes "src/lib/${i}/Makefile.am")
+       # generate the list of valid dependencies for the current library (take compilation order into account)
+       VALID_LIST=$(extract_dependencies "${REVERSE_LIBRARIES_LIST}" "${i}")
+       compute_dependencies "${i}" "src/lib"
+       PATH_TO_NAME=$(echo "src/lib" | tr -s "/" "_")
+       declare COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${i}="${SORTED_DEPENDENCIES}"
+done
+
+# remove empty spaces
+BASE_LIBRARIES_MAKEFILES=$(echo "${BASE_LIBRARIES_MAKEFILES}" | xargs | tr -s " " "\n")
+
+echo "base Makefiles.am files:"
+echo "${BASE_LIBRARIES_MAKEFILES}"
+
+OTHER_MAKEFILES=$(echo "${MAKEFILES_LIST}" | tr -s " " "\n" | grep -v "src/lib/" | grep -v "src/share/" | grep -v "src/Makefile.am")
+# remove empty spaces
+OTHER_MAKEFILES=$(echo "${OTHER_MAKEFILES}" | xargs | tr -s " " "\n")
+
+echo "remaining Makefile.am files:"
+echo "${OTHER_MAKEFILES}"
+
+for i in ${OTHER_MAKEFILES}; do
+       # extract dependencies found in the artifact folder
+       BASE_DEPENDENCIES=$(extract_includes "${i}")
+       # generate the list of valid dependencies for the current artifact (take compilation order into account)
+       VALID_LIST="${REVERSE_LIBRARIES_LIST}"
+       ARTIFACT=$(echo "${i}" | rev | cut -d "/" -f 2 | rev)
+       ARTIFACT_PATH=$(echo "${i}" | rev | cut -d "/" -f 3- | rev)
+       compute_dependencies "${ARTIFACT}" "${ARTIFACT_PATH}"
+       PATH_TO_NAME=$(echo "${ARTIFACT_PATH}" | tr -s "/" "_")
+       declare COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${ARTIFACT}="${SORTED_DEPENDENCIES}"
+done
+
+exit
+