From 218f7033a06ec2244636c348753df22d92dea57f Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Fri, 1 Dec 2017 12:06:59 +0100 Subject: [PATCH] [5442] Implemented NetworkState class. --- src/lib/dhcpsrv/Makefile.am | 1 + src/lib/dhcpsrv/network_state.cc | 145 ++++++++++++++++++ src/lib/dhcpsrv/network_state.h | 140 +++++++++++++++++ src/lib/dhcpsrv/tests/Makefile.am | 1 + .../dhcpsrv/tests/network_state_unittest.cc | 137 +++++++++++++++++ 5 files changed, 424 insertions(+) create mode 100644 src/lib/dhcpsrv/network_state.cc create mode 100644 src/lib/dhcpsrv/network_state.h create mode 100644 src/lib/dhcpsrv/tests/network_state_unittest.cc diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index cc3e148634..de492e611b 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -140,6 +140,7 @@ endif libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h libkea_dhcpsrv_la_SOURCES += network.cc network.h +libkea_dhcpsrv_la_SOURCES += network_state.cc network_state.h if HAVE_PGSQL libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h diff --git a/src/lib/dhcpsrv/network_state.cc b/src/lib/dhcpsrv/network_state.cc new file mode 100644 index 0000000000..3b29f66789 --- /dev/null +++ b/src/lib/dhcpsrv/network_state.cc @@ -0,0 +1,145 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Implementation of the @c NetworkState class. +class NetworkStateImpl : public boost::enable_shared_from_this { +public: + + /// @brief Constructor. + NetworkStateImpl() + : globally_disabled_(false), disabled_subnets_(), disabled_networks_(), + timer_present_(false), timer_mgr_(TimerMgr::instance()) { + } + + /// @brief Globally disables or enables DHCP service. + void setDisableService(const bool disable) { + globally_disabled_ = disable; + } + + /// @brief Enables DHCP service globally and per scopes. + /// + /// If delayed enabling DHCP service has been scheduled, it cancels it. + void enableAll() { + setDisableService(false); + + /// @todo Enable service for all subnets and networks here. + + destroyTimer(); + } + + /// @brief Creates a timer couting the time when @c enableAll should be + /// automatically called. + /// + /// If the timer has been already scheduled, it is destroyed and replaced + /// with a new timer. + /// + /// @param seconds Number of seconds to elapse before the @c enableAll is + /// called. + void createTimer(const unsigned int seconds) { + destroyTimer(); + timer_mgr_->registerTimer("network-state-timer", + boost::bind(&NetworkStateImpl::enableAll, + shared_from_this()), + seconds * 1000, + asiolink::IntervalTimer::ONE_SHOT); + timer_mgr_->setup("network-state-timer"); + timer_present_ = true; + } + + /// @brief Destroys a timer if present. + void destroyTimer() { + if (timer_present_) { + timer_mgr_->unregisterTimer("network-state-timer"); + timer_present_ = false; + } + } + + /// @brief A flag indicating if DHCP service is globally disabled. + bool globally_disabled_; + + /// @brief A list of subnets for which the DHCP service has been disabled. + NetworkState::Subnets disabled_subnets_; + + /// @brief A list of networks for which the DHCP service has been disabled. + NetworkState::Networks disabled_networks_; + + /// @brief Boolean flag indicating if the delayed enabling of the DHCP service + /// has been scheduled. + bool timer_present_; + + /// @brief A pointer to the common timer manager. + /// + /// This pointer is held here to make sure that the timer manager is not + /// destroyed before an instance of this class is destroyed. + TimerMgrPtr timer_mgr_; +}; + +NetworkState::NetworkState() + : impl_(new NetworkStateImpl()) { +} + +void +NetworkState::disableService() { + impl_->setDisableService(true); +} + +void +NetworkState::enableService() { + impl_->setDisableService(false); +} + +void +NetworkState::enableAll() { + impl_->enableAll(); +} + +void +NetworkState::delayedEnableAll(const unsigned int seconds) { + impl_->createTimer(seconds); +} + +bool +NetworkState::isServiceEnabled() const { + return (!impl_->globally_disabled_); +} + +bool +NetworkState::isDelayedEnableAll() const { + return (impl_->timer_present_); +} + +void +NetworkState::selectiveDisable(const NetworkState::Subnets& subnets) { + isc_throw(NotImplemented, "selectiveDisableService is not implemented"); +} + +void +NetworkState::selectiveDisable(const NetworkState::Networks& networks) { + isc_throw(NotImplemented, "selectiveDisableService is not implemented"); +} + +void +NetworkState::selectiveEnable(const NetworkState::Subnets& subnets) { + isc_throw(NotImplemented, "selectiveEnableService is not implemented"); +} + +void +NetworkState::selectiveEnable(const NetworkState::Networks& networks) { + isc_throw(NotImplemented, "selectiveEnableService is not implemented"); +} + + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/network_state.h b/src/lib/dhcpsrv/network_state.h new file mode 100644 index 0000000000..300d91dff1 --- /dev/null +++ b/src/lib/dhcpsrv/network_state.h @@ -0,0 +1,140 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef NETWORK_STATE_H +#define NETWORK_STATE_H + +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +class NetworkStateImpl; + +/// @brief Holds information about service enabling for specific networks. +/// +/// When the DHCP server receives a command to disable DHCP service entirely +/// or for specific networks, this has to be recorded to allow for re-enabling +/// DHCP service for these networks as a result of receiving a command from +/// the administrator or when the tiemout for re-enabling the service occurs. +/// +/// In the future, it will be possible to specify "disabled" parameter for +/// a subnet or network to indicate that this subnet should be excluded from +/// the service. When a command is subsequently sent to disable a service for +/// some other subnets and the timeout value is specified, only those subnets +/// for which a timeout has been specified should be re-enabled. This class +/// fulfils this requirement by re-enabling only those subnets which have been +/// temporarily disabled. The subnets specified as "disabled" in the configuration +/// file should remain disabled until explcitly enabled with a control command. +/// +/// Disabling DHCP service with a timeout is useful to guard against cases when +/// the controlling client dies after disabling the DHCP service on the server, +/// e.g. failover peers may instruct each other to disable the DHCP service while +/// database synchronization takes place. If the peer subsequently dies, the +/// surviving server must re-enable DHCP on its own. +/// +/// @todo This class currently supports only the case of completely disabling +/// the DHCP service. Disabling per network/subnet will be added later. +class NetworkState { +public: + + /// @brief Type of the container holding collection of subnet identifiers. + typedef std::set Subnets; + + /// @brief Type of the container holding collection of shared network names. + typedef std::set Networks; + + /// @brief Constructor. + NetworkState(); + + /// @brief Globally disables DHCP service. + /// + /// The DHCP service becomes disabled for all subnets and networks, + /// regardless of the per scope settings. + void disableService(); + + /// @brief Globally enables DHCP service. + /// + /// The DHCP service becomes enabled but per scope settings are in effect. + /// In order to enable the service for all scopes previously disabled with + /// a control command, use @c enableAll. + void enableService(); + + /// @brief Enables DHCP service globally and for scopes which have been + /// disabled as a result of control command. + void enableAll(); + + /// @brief Schedules enabling DHCP service in the future. + /// + /// @param seconds Number of seconds after which the service should be enabled + /// unless @c enableAll is enabled before that time. + void delayedEnableAll(const unsigned int seconds); + + /// @brief Checks if the DHCP service is globally enabled. + /// + /// @return true if the service is globally enabled, false otherwise. + bool isServiceEnabled() const; + + /// @brief Checks if delayed enabling of DHCP services is scheduled. + /// + /// It indicates that the timer is present which counts the time until + /// @c enableAll function will be called automatically. + /// + /// @return true if delayed enabling of the DHCP service is scheduled, + /// false otherwise. + bool isDelayedEnableAll() const; + + /// @name Selective disabling/enabling DHCP service per scopes + //@{ + + /// @brief Disable DHCP service for selected subnets. + /// + /// @param subnets Collection of subnet identifiers for which the service + /// should be disabled. + /// + /// @throw isc::NotImplemented + void selectiveDisable(const NetworkState::Subnets& subnets); + + /// @brief Disable DHCP service for selected networks. + /// + /// @param networks Collection of shared network names for which the service + /// should be disabled. + /// + /// @throw isc::NotImplemented + void selectiveDisable(const NetworkState::Networks& networks); + + /// @brief Enable DHCP service for selected subnets. + /// + /// @param subnets Collection of subnet identifiers for which the service + /// should be disabled. + /// + /// @throw isc::NotImplemented + void selectiveEnable(const NetworkState::Subnets& subnets); + + /// @brief Enable DHCP service for selected networks. + /// + /// @param networks Collection of shared network names for which the service + /// should be enabled. + /// + /// @throw isc::NotImplemented + void selectiveEnable(const NetworkState::Networks& networks); + + //@} + +private: + + /// @brief Pointer to the @c NetworkState implementation. + boost::shared_ptr impl_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // NETWORK_STATE_H diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 342ed921d8..8f55790f3c 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -133,6 +133,7 @@ libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_hand libdhcpsrv_unittests_SOURCES += triplet_unittest.cc libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h libdhcpsrv_unittests_SOURCES += timer_mgr_unittest.cc +libdhcpsrv_unittests_SOURCES += network_state_unittest.cc libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) if HAVE_MYSQL diff --git a/src/lib/dhcpsrv/tests/network_state_unittest.cc b/src/lib/dhcpsrv/tests/network_state_unittest.cc new file mode 100644 index 0000000000..13f6396d73 --- /dev/null +++ b/src/lib/dhcpsrv/tests/network_state_unittest.cc @@ -0,0 +1,137 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture class for @c NetworkState class. +class NetworkStateTest : public ::testing::Test { +public: + + /// @brief Constructor. + NetworkStateTest() + : io_service_(new IOService()), + test_timer_(*io_service_) { + TimerMgr::instance()->unregisterTimers(); + TimerMgr::instance()->setIOService(io_service_); + } + + /// @brief Destructor. + virtual ~NetworkStateTest() { + // Cancel timers. + TimerMgr::instance()->unregisterTimers(); + test_timer_.cancel(); + // Make sure IO service will stop when no timers are scheduled. + io_service_->stopWork(); + // Run outstanding tasks. + io_service_->run(); + } + + /// @brief Test timer callback stopping IO service. + void testTimerCallback() { + TimerMgr::instance()->unregisterTimers(); + io_service_->stop(); + } + + /// @brief Runs IO service with a timeout. + /// + /// @param timeout_ms Timeout for runninr IO service in milliseconds. + void runIOService(const long timeout_ms) { + test_timer_.setup(boost::bind(&NetworkStateTest::testTimerCallback, this), timeout_ms, + IntervalTimer::ONE_SHOT); + io_service_->run(); + } + + /// @brief IO service used during the tests. + IOServicePtr io_service_; + + /// @brief Timeout detecting timer. + IntervalTimer test_timer_; +}; + +// This test verifies that it is possible to disable and then enable service. +TEST_F(NetworkStateTest, disableEnableService) { + NetworkState state; + state.disableService(); + EXPECT_FALSE(state.isServiceEnabled()); + state.enableService(); + EXPECT_TRUE(state.isServiceEnabled()); +} + +// This test verifies that enableAll() enables the service. This test will be extended +// in the future to verify that it also enables disabled scopes. +TEST_F(NetworkStateTest, enableAll) { + NetworkState state; + state.disableService(); + EXPECT_FALSE(state.isServiceEnabled()); + state.enableAll(); + EXPECT_TRUE(state.isServiceEnabled()); +} + +// This test verifies that it is possible to setup delayed execution of enableAll +// function. +TEST_F(NetworkStateTest, delayedEnableAll) { + NetworkState state; + // Disable the service and then schedule enabling it in 1 second. + state.disableService(); + state.delayedEnableAll(1); + // Initially the service should be still disabled. + EXPECT_FALSE(state.isServiceEnabled()); + // After running IO service for 2 seconds, the service should be enabled. + runIOService(2000); + EXPECT_TRUE(state.isServiceEnabled()); +} + +// This test verifies that explicitly enabling the service cancels the timer +// scheduled for automatically enabling it. +TEST_F(NetworkStateTest, earlyEnableAll) { + NetworkState state; + // Disable the service. + state.disableService(); + EXPECT_FALSE(state.isServiceEnabled()); + // Schedule enabling the service in 2 seconds. + state.delayedEnableAll(2); + // Explicitly enable the service. + state.enableAll(); + // The timer should be now canceled and the service should be enabled. + EXPECT_FALSE(state.isDelayedEnableAll()); + EXPECT_TRUE(state.isServiceEnabled()); +} + +// This test verifies that it is possible to call delayedEnableAll multiple times +// and that it results in only one timer being scheduled. +TEST_F(NetworkStateTest, multipleDelayedEnableAll) { + NetworkState state; + // Disable the service and then schedule enabling it in 1 second. + state.disableService(); + // Schedule the first timer for 5 seconds. + state.delayedEnableAll(5); + // When calling it the second time the old timer should be destroyed and + // the timeout should be set to 2 seconds. + state.delayedEnableAll(2); + // Initially the service should be still disabled. + EXPECT_FALSE(state.isServiceEnabled()); + // After running IO service for 3 seconds, the service should be enabled. + runIOService(3000); + EXPECT_TRUE(state.isServiceEnabled()); + // The timer should not be present, even though the first timer was created + // with 5 seconds interval. + EXPECT_FALSE(state.isDelayedEnableAll()); +} + + +} // end of anonymous namespace -- 2.47.2