// safe and we really don't want to emit exceptions to whoever called this
// method. Instead, catch an exception and create appropriate answer.
try {
- CfgMgr::instance().getConfiguration()->cfg_iface_
- .openSockets(srv->getPort(), getInstance()->useBroadcast());
+ CfgMgr::instance().getCurrent()->getCfgIface()
+ .openSockets(CfgIface::V4, srv->getPort(),
+ getInstance()->useBroadcast());
} catch (std::exception& ex) {
err << "failed to open sockets after server reconfiguration: "
parser = new Uint32Parser(config_id,
globalContext()->uint32_values_);
} else if (config_id.compare("interfaces") == 0) {
- parser = new InterfaceListConfigParser(config_id);
+ parser = new InterfaceListConfigParser(config_id, globalContext());
} else if (config_id.compare("subnet4") == 0) {
parser = new Subnets4ListConfigParser(config_id);
} else if (config_id.compare("option-data") == 0) {
subnet_parser->commit();
}
- if (iface_parser) {
- iface_parser->commit();
- }
+ // No need to commit interface names as this is handled by the
+ // CfgMgr::commit() function.
// Apply global options
commitGlobalOptions();
// Rollback changes as the configuration parsing failed.
if (rollback) {
globalContext().reset(new ParserContext(original_context));
+ CfgMgr::instance().rollback();
return (answer);
}
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp4;
isc::data::ConstElementPtr logger;
// If there's no logging element, we'll just pass NULL pointer,
// which will be handled by configureLogger().
Daemon::configureLogger(json->get("Logging"),
- CfgMgr::instance().getConfiguration(),
+ CfgMgr::instance().getStaging(),
ControlledDhcpv4Srv::getInstance()->getVerbose());
// Get Dhcp4 component from the config
ASSERT_TRUE(status);
checkResult(status, 0);
- CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+ CfgMgr::instance().getStaging()->
+ getCfgIface().openSockets(CfgIface::V4, 10000);
// eth0 and eth1 were explicitly selected. eth2 was not.
EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
ASSERT_TRUE(status);
checkResult(status, 0);
- CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+ CfgMgr::instance().getStaging()->
+ getCfgIface().openSockets(CfgIface::V4, 10000);
// All interfaces should be now active.
ASSERT_TRUE(test_config.socketOpen("eth0", AF_INET));
pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
subnet_->addPool(pool_);
- CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+ CfgMgr::instance().clear();
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().addSubnet4(subnet_);
Dhcpv4SrvTest::~Dhcpv4SrvTest() {
// Make sure that we revert to default value
- CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+ CfgMgr::instance().clear();
CfgMgr::instance().echoClientId(true);
}
// safe and we really don't want to emit exceptions to the callback caller.
// Instead, catch an exception and create appropriate answer.
try {
- CfgMgr::instance().getConfiguration()->cfg_iface_
- .openSockets(srv->getPort());
+ CfgMgr::instance().getConfiguration()->getCfgIface()
+ .openSockets(CfgIface::V6, srv->getPort());
} catch (const std::exception& ex) {
std::ostringstream err;
// Instantiate allocation engine
alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
- // We have to point out to the CfgMgr that the we are in the IPv6
- // domain, so as the IPv6 sockets are opened rather than IPv4 sockets
- // which are the default.
- CfgMgr::instance().getConfiguration()
- ->cfg_iface_.setFamily(CfgIface::V6);
-
/// @todo call loadLibraries() when handling configuration changes
} catch (const std::exception &e) {
parser = new Uint32Parser(config_id,
globalContext()->uint32_values_);
} else if (config_id.compare("interfaces") == 0) {
- parser = new InterfaceListConfigParser(config_id);
+ parser = new InterfaceListConfigParser(config_id, globalContext());
} else if (config_id.compare("subnet6") == 0) {
parser = new Subnets6ListConfigParser(config_id);
} else if (config_id.compare("option-data") == 0) {
subnet_parser->commit();
}
- if (iface_parser) {
- iface_parser->commit();
- }
+ // No need to commit interface names as this is handled by the
+ // CfgMgr::commit() function.
// This occurs last as if it succeeds, there is no easy way to
// revert it. As a result, the failure to commit a subsequent
// Rollback changes as the configuration parsing failed.
if (rollback) {
globalContext().reset(new ParserContext(original_context));
+ CfgMgr::instance().rollback();
return (answer);
}
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp6;
isc::data::ConstElementPtr logger;
// properly test interface configuration we disable listening on
// all interfaces before each test and later check that this setting
// has been overriden by the configuration used in the test.
- CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
- CfgMgr::instance().getConfiguration()->
- cfg_iface_.setFamily(CfgIface::V6);
+ CfgMgr::instance().clear();
// Create fresh context.
globalContext()->copyContext(ParserContext(Option::V6));
}
// as the pool does not belong to that subnet
checkResult(status, 0);
- CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+ CfgMgr::instance().getStaging()->
+ getCfgIface().openSockets(CfgIface::V6, 10000);
// eth0 and eth1 were explicitly selected. eth2 was not.
EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
ConstElementPtr status;
// This configuration specifies two interfaces on which server should listen
- // bu also includes keyword 'all'. This keyword switches server into the
+ // but also includes '*'. This keyword switches server into the
// mode when it listens on all interfaces regardless of what interface names
// were specified in the "interfaces" parameter.
string config = "{ \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ],"
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
checkResult(status, 0);
- CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+ CfgMgr::instance().getStaging()->
+ getCfgIface().openSockets(CfgIface::V6, 10000);
// All interfaces should be now active.
EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
const char* CfgIface::ALL_IFACES_KEYWORD = "*";
-CfgIface::CfgIface(Family family)
- : family_(family),
- wildcard_used_(false) {
+CfgIface::CfgIface()
+ : wildcard_used_(false) {
}
void
-CfgIface::closeSockets() {
+CfgIface::closeSockets() const {
IfaceMgr::instance().closeSockets();
}
void
-CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
+CfgIface::openSockets(const Family& family, const uint16_t port,
+ const bool use_bcast) const {
// If wildcard interface '*' was not specified, set all interfaces to
// inactive state. We will later enable them selectively using the
// interface names specified by the user. If wildcard interface was
// specified, mark all interfaces active. In all cases, mark loopback
// inactive.
- setState(!wildcard_used_, true);
+ setState(family, !wildcard_used_, true);
// Remove selection of unicast addresses from all interfaces.
IfaceMgr::instance().clearUnicasts();
// If there is no wildcard interface specified, we will have to iterate
<< *iface_name << "' as this interface doesn't"
" exist");
- } else if (getFamily() == V4) {
+ } else if (family == V4) {
iface->inactive4_ = false;
} else {
}
// Select unicast sockets. It works only for V6. Ignore for V4.
- if (getFamily() == V6) {
+ if (family == V6) {
for (UnicastMap::const_iterator unicast = unicast_map_.begin();
unicast != unicast_map_.end(); ++unicast) {
Iface* iface = IfaceMgr::instance().getIface(unicast->first);
IfaceMgrErrorMsgCallback error_callback =
boost::bind(&CfgIface::socketOpenErrorHandler, _1);
bool sopen;
- if (getFamily() == V4) {
+ if (family == V4) {
sopen = IfaceMgr::instance().openSockets4(port, use_bcast,
error_callback);
} else {
}
void
-CfgIface::setState(const bool inactive, const bool loopback_inactive) {
+CfgIface::setState(const Family& family, const bool inactive,
+ const bool loopback_inactive) const {
IfaceMgr::IfaceCollection ifaces = IfaceMgr::instance().getIfaces();
for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
- if (getFamily() == V4) {
+ if (family == V4) {
iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
loopback_inactive : inactive;
} else {
}
void
-CfgIface::use(const std::string& iface_name) {
+CfgIface::use(const Family& family, const std::string& iface_name) {
// The interface name specified may have two formats, e.g.:
// - eth0
// - eth0/2001:db8:1::1.
}
- } else if (getFamily() == V4) {
+ } else if (family == V4) {
isc_throw(InvalidIfaceName, "unicast addresses in the format of: "
"iface-name/unicast-addr_stress can only be specified for"
" IPv6 addr_stress family");
};
/// @brief Constructor.
- ///
- /// @param family Protocol family (default is V4).
- CfgIface(Family family = V4);
+ CfgIface();
/// @brief Convenience function which closes all open sockets.
- void closeSockets();
-
- /// @brief Returns protocol family used by the @c CfgIface.
- Family getFamily() const {
- return (family_);
- }
+ void closeSockets() const;
/// @brief Tries to open sockets on selected interfaces.
///
/// documentation for details how to specify interfaces and unicast
/// addresses to bind the sockets to.
///
+ /// @param family Address family (v4 or v6).
/// @param port Port number to be used to bind sockets to.
/// @param use_bcast A boolean flag which indicates if the broadcast
/// traffic should be received through the socket. This parameter is
/// ignored for IPv6.
- void openSockets(const uint16_t port, const bool use_bcast = true);
+ void openSockets(const Family& family, const uint16_t port,
+ const bool use_bcast = true) const;
/// @brief Puts the interface configuration into default state.
///
/// This function removes interface names from the set.
void reset();
- /// @brief Sets protocol family.
- ///
- /// @param family New family value (V4 or V6).
- void setFamily(Family family) {
- family_ = family;
- }
-
/// @brief Select interface to be used to receive DHCP traffic.
///
/// This function controls the selection of the interface on which the
/// not allowed when specifying a unicast address. For example:
/// */2001:db8:1::1 is not allowed.
///
+ /// @param family Address family (v4 or v6).
/// @param iface_name Explicit interface name, a wildcard name (*) of
/// the interface(s) or the pair of iterface/unicast-address to be used
/// to receive DHCP traffic.
/// @throw DuplicateIfaceName If the interface is already selected, i.e.
/// @throw IOError when specified unicast address is invalid.
/// @c CfgIface::use has been already called for this interface.
- void use(const std::string& iface_name);
+ void use(const Family& family, const std::string& iface_name);
private:
/// This function selects all interfaces to receive DHCP traffic or
/// deselects all interfaces so as none of them receives a DHCP traffic.
///
+ /// @param family Address family (v4 or v6).
/// @param inactive A boolean value which indicates if all interfaces
/// (except loopback) should be selected or deselected.
/// @param loopback_inactive A boolean value which indicates if loopback
/// interface should be selected or deselected.
/// should be deselected/inactive (true) or selected/active (false).
- void setState(const bool inactive, const bool loopback_inactive);
+ void setState(const Family& family, const bool inactive,
+ const bool loopback_inactive) const;
/// @brief Error handler for executed when opening a socket fail.
///
return (d2_client_mgr_);
}
+void
+CfgMgr::ensureCurrentAllocated() {
+ if (!configuration_ || configs_.empty()) {
+ configuration_.reset(new Configuration());
+ configs_.push_back(configuration_);
+ }
+}
+
+void
+CfgMgr::clear() {
+ configs_.clear();
+ ensureCurrentAllocated();
+}
+
+void
+CfgMgr::commit() {
+ if (!configs_.empty() && configs_.back() != configuration_) {
+ configuration_ = configs_.back();
+ }
+}
+
+void
+CfgMgr::rollback() {
+ ensureCurrentAllocated();
+ if (!configuration_->sequenceEquals(*configs_.back())) {
+ configs_.pop_back();
+ }
+}
+
ConfigurationPtr
CfgMgr::getConfiguration() {
return (configuration_);
}
+ConstConfigurationPtr
+CfgMgr::getCurrent() {
+ ensureCurrentAllocated();
+ return (configuration_);
+}
+
+ConfigurationPtr
+CfgMgr::getStaging() {
+ ensureCurrentAllocated();
+ if (configuration_->sequenceEquals(*configs_.back())) {
+ uint32_t sequence = configuration_->getSequence();
+ configs_.push_back(ConfigurationPtr(new Configuration(++sequence)));
+ }
+ return (configs_.back());
+}
+
CfgMgr::CfgMgr()
: datadir_(DHCP_DATA_DIR), echo_v4_client_id_(true),
- d2_client_mgr_(), configuration_(new Configuration()) {
+ d2_client_mgr_() {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am
+ ensureCurrentAllocated();
}
CfgMgr::~CfgMgr() {
/// @return a reference to the DHCP-DDNS manager.
D2ClientMgr& getD2ClientMgr();
+ void clear();
+
+ void commit();
+
+ void rollback();
/// @brief Returns the current configuration.
///
/// @return a pointer to the current configuration.
ConfigurationPtr getConfiguration();
+ ConstConfigurationPtr getCurrent();
+
+ ConfigurationPtr getStaging();
+
protected:
/// @brief Protected constructor.
private:
+ void ensureCurrentAllocated();
+
/// @brief Checks that the IPv4 subnet with the given id already exists.
///
/// @param subnet Subnet for which this function will check if the other
/// @todo: maybe this should be a vector<Configuration>, so we could keep
/// previous configurations and do a rollback if needed?
ConfigurationPtr configuration_;
+
+ /// @name Configuration List.
+ ///
+ //@{
+ /// @brief Configuration list type.
+ typedef std::list<ConfigurationPtr> ConfigurationList;
+
+ /// @brief Container holding all previous and current configurations.
+ ConfigurationList configs_;
+ //@}
};
} // namespace isc::dhcp
namespace isc {
namespace dhcp {
+Configuration::Configuration()
+ : sequence_(0) {
+}
+
+Configuration::Configuration(uint32_t sequence)
+ : sequence_(sequence) {
+}
+
std::string
Configuration::getConfigSummary(const uint32_t selection) const {
std::ostringstream s;
return (summary);
}
+bool
+Configuration::sequenceEquals(const Configuration& other) {
+ return (getSequence() == other.getSequence());
+}
+
}
}
/// @brief Specifies current DHCP configuration
///
/// @todo Migrate all other configuration parameters from cfgmgr.h here
-struct Configuration {
-
+class Configuration {
+public:
/// @name Constants for selection of parameters returned by @c getConfigSummary
///
//@{
/// @brief logging specific information
LoggingInfoStorage logging_info_;
- /// @brief Interface configuration.
+ /// @brief Default constructor.
///
- /// Used to select interfaces on which the DHCP server will listen to
- /// queries.
- CfgIface cfg_iface_;
+ /// This constructor sets configuration sequence number to 0.
+ Configuration();
+
+ /// @brief Constructor.
+ ///
+ /// Sets arbitrary configuration sequence number.
+ Configuration(uint32_t sequence);
/// @brief Returns summary of the configuration in the textual format.
///
///
/// @return Summary of the configuration in the textual format.
std::string getConfigSummary(const uint32_t selection) const;
+
+ /// @brief Returns configuration sequence number.
+ uint32_t getSequence() const {
+ return (sequence_);
+ }
+
+ /// @brief Compares configuration sequence with other sequence.
+ ///
+ /// This method compares sequence numbers of two configurations for
+ /// equality. The sequence numbers are meant to be unique, so if
+ /// they are equal it means that they point to the same configuration.
+ ///
+ /// @param other Configuration which sequence number should be
+ /// compared with the sequence number of this configuration.
+ ///
+ /// @return true if sequence numbers are equal.
+ bool sequenceEquals(const Configuration& other);
+
+ /// @brief Returns object which represents selection of interfaces.
+ ///
+ /// This function returns a reference to the object which represents the
+ /// set of interfaces being used to receive DHCP traffic.
+ ///
+ /// @return Object representing selection of interfaces.
+ const CfgIface& getCfgIface() const {
+ return (cfg_iface_);
+ }
+
+ /// @brief Sets the object representing selection of interfaces.
+ ///
+ /// @param cfg_iface Object representing selection of interfaces.
+ void setCfgIface(const CfgIface& cfg_iface) {
+ cfg_iface_ = cfg_iface;
+ }
+
+private:
+
+ /// @brief Sequence number identifying the configuration.
+ uint32_t sequence_;
+
+ /// @brief Interface configuration.
+ ///
+ /// Used to select interfaces on which the DHCP server will listen to
+ /// queries.
+ CfgIface cfg_iface_;
+
};
-/// @brief pointer to the configuration
+/// @name Pointers to the @c Configuration object.
+///
+//@{
+/// @brief Non-const pointer to the @ Configuration.
typedef boost::shared_ptr<Configuration> ConfigurationPtr;
+/// @brief Const pointer to the @c Configuration.
+typedef boost::shared_ptr<const Configuration> ConstConfigurationPtr;
+//@}
+
} // namespace isc::dhcp
} // namespace isc
// ******************** InterfaceListConfigParser *************************
InterfaceListConfigParser::
-InterfaceListConfigParser(const std::string& param_name)
- : param_name_(param_name) {
+InterfaceListConfigParser(const std::string& param_name,
+ ParserContextPtr global_context)
+ : param_name_(param_name), global_context_(global_context) {
if (param_name_ != "interfaces") {
isc_throw(BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
void
InterfaceListConfigParser::build(ConstElementPtr value) {
- // Copy the current interface configuration.
- ConfigurationPtr config = CfgMgr::instance().getConfiguration();
- cfg_iface_ = config->cfg_iface_;
- cfg_iface_.reset();
+ CfgIface cfg_iface;
BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
std::string iface_name = iface->stringValue();
try {
- cfg_iface_.use(iface_name);
+ cfg_iface.use(global_context_->universe_ == Option::V4 ?
+ CfgIface::V4 : CfgIface::V6, iface_name);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Failed to select interface: "
<< ex.what() << " (" << value->getPosition() << ")");
}
}
+ CfgMgr::instance().getStaging()->setCfgIface(cfg_iface);
}
void
InterfaceListConfigParser::commit() {
- // Use the new configuration created in a build time.
- CfgMgr::instance().getConfiguration()->cfg_iface_ = cfg_iface_;
-}
-
-bool
-InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
-
- for (IfaceListStorage::const_iterator it = interfaces_.begin();
- it != interfaces_.end(); ++it) {
- if (iface == *it) {
- return (true);
- }
- }
- return (false);
+ // Nothing to do.
}
// ******************** HooksLibrariesParser *************************
/// "interface" parameter only. All other types will throw exception.
///
/// @param param_name name of the configuration parameter being parsed
+ /// @param global_context Global parser context.
/// @throw BadValue if supplied parameter name is not "interface"
- InterfaceListConfigParser(const std::string& param_name);
+ InterfaceListConfigParser(const std::string& param_name,
+ ParserContextPtr global_context);
/// @brief parses parameters value
///
/// @param value pointer to the content of parsed values
virtual void build(isc::data::ConstElementPtr value);
- /// @brief Assignes a parsed list of interfaces to the configuration.
- ///
- /// This is exception safe operation.
+ /// @brief Does nothing.
virtual void commit();
private:
- /// @brief Check that specified interface exists in
- /// @c InterfaceListConfigParser::interfaces_.
- ///
- /// @param iface A name of the interface.
- ///
- /// @return true if specified interface name was found.
- bool isIfaceAdded(const std::string& iface) const;
-
- /// contains list of network interfaces
- typedef std::list<std::string> IfaceListStorage;
- IfaceListStorage interfaces_;
// Parsed parameter name
std::string param_name_;
- /// Holds the configuration created during
- CfgIface cfg_iface_;
+ /// Global parser context.
+ ParserContextPtr global_context_;
};
/// @brief Parser for hooks library list
// This test checks that the interface names can be explicitly selected
// by their names and IPv4 sockets are opened on these interfaces.
TEST_F(CfgIfaceTest, explicitNamesV4) {
- CfgIface cfg(CfgIface::V4);
+ CfgIface cfg;
// Specify valid interface names. There should be no error.
- ASSERT_NO_THROW(cfg.use("eth0"));
- ASSERT_NO_THROW(cfg.use("eth1"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V4, "eth0"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V4, "eth1"));
// Open sockets on specified interfaces.
- cfg.openSockets(DHCP4_SERVER_PORT);
+ cfg.openSockets(CfgIface::V4, DHCP4_SERVER_PORT);
// Sockets should be now open on eth0 and eth1, but not on loopback.
EXPECT_TRUE(socketOpen("eth0", AF_INET));
// Reset configuration and select only one interface this time.
cfg.reset();
- ASSERT_NO_THROW(cfg.use("eth1"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V4, "eth1"));
- cfg.openSockets(DHCP4_SERVER_PORT);
+ cfg.openSockets(CfgIface::V4, DHCP4_SERVER_PORT);
// Socket should be open on eth1 only.
EXPECT_FALSE(socketOpen("eth0", AF_INET));
// This test checks that the interface names can be explicitly selected
// by their names and IPv6 sockets are opened on these interfaces.
TEST_F(CfgIfaceTest, explicitNamesV6) {
- CfgIface cfg(CfgIface::V6);
+ CfgIface cfg;
// Specify valid interface names. There should be no error.
- ASSERT_NO_THROW(cfg.use("eth0"));
- ASSERT_NO_THROW(cfg.use("eth1"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "eth0"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "eth1"));
// Open sockets on specified interfaces.
- cfg.openSockets(DHCP6_SERVER_PORT);
+ cfg.openSockets(CfgIface::V6, DHCP6_SERVER_PORT);
// Sockets should be now open on eth0 and eth1, but not on loopback.
EXPECT_TRUE(socketOpen("eth0", AF_INET6));
// Reset configuration and select only one interface this time.
cfg.reset();
- ASSERT_NO_THROW(cfg.use("eth1"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "eth1"));
- cfg.openSockets(DHCP6_SERVER_PORT);
+ cfg.openSockets(CfgIface::V6, DHCP6_SERVER_PORT);
// Socket should be open on eth1 only.
EXPECT_FALSE(socketOpen("eth0", AF_INET6));
// This test checks that the wildcard interface name can be specified to
// select all interfaces to open IPv4 sockets.
TEST_F(CfgIfaceTest, wildcardV4) {
- CfgIface cfg(CfgIface::V4);
- ASSERT_NO_THROW(cfg.use("*"));
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(CfgIface::V4, "*"));
- cfg.openSockets(DHCP4_SERVER_PORT);
+ cfg.openSockets(CfgIface::V4, DHCP4_SERVER_PORT);
// Sockets should be now open on eth0 and eth1, but not on loopback.
EXPECT_TRUE(socketOpen("eth0", AF_INET));
// This test checks that the wildcard interface name can be specified to
// select all interfaces to open IPv6 sockets.
TEST_F(CfgIfaceTest, wildcardV6) {
- CfgIface cfg(CfgIface::V6);
- ASSERT_NO_THROW(cfg.use("*"));
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "*"));
- cfg.openSockets(DHCP4_SERVER_PORT);
+ cfg.openSockets(CfgIface::V6, DHCP6_SERVER_PORT);
// Sockets should be now open on eth0 and eth1, but not on loopback.
EXPECT_TRUE(socketOpen("eth0", AF_INET6));
// the interface on which the socket bound to link local address is also
// opened.
TEST_F(CfgIfaceTest, validUnicast) {
- CfgIface cfg(CfgIface::V6);
+ CfgIface cfg;
// One socket will be opened on link-local address, one on unicast but
// on the same interface.
- ASSERT_NO_THROW(cfg.use("eth0"));
- ASSERT_NO_THROW(cfg.use("eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "eth0"));
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "eth0/2001:db8:1::1"));
- cfg.openSockets(DHCP6_SERVER_PORT);
+ cfg.openSockets(CfgIface::V6, DHCP6_SERVER_PORT);
EXPECT_TRUE(socketOpen("eth0", AF_INET6));
EXPECT_TRUE(unicastOpen("eth0"));
// Test that when invalid interface names are specified an exception is thrown.
TEST_F(CfgIfaceTest, invalidValues) {
- CfgIface cfg(CfgIface::V4);
- ASSERT_THROW(cfg.use(""), InvalidIfaceName);
- ASSERT_THROW(cfg.use(" "), InvalidIfaceName);
- ASSERT_THROW(cfg.use("bogus"), NoSuchIface);
-
- ASSERT_NO_THROW(cfg.use("eth0"));
- ASSERT_THROW(cfg.use("eth0"), DuplicateIfaceName);
-
- ASSERT_THROW(cfg.use("eth0/2001:db8:1::1"), InvalidIfaceName);
-
- cfg.setFamily(CfgIface::V6);
-
- ASSERT_THROW(cfg.use("eth0/"), InvalidIfaceName);
- ASSERT_THROW(cfg.use("/2001:db8:1::1"), InvalidIfaceName);
- ASSERT_THROW(cfg.use("*/2001:db8:1::1"), InvalidIfaceName);
- ASSERT_THROW(cfg.use("bogus/2001:db8:1::1"), NoSuchIface);
- ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
- ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
- ASSERT_THROW(cfg.use("eth0/2001:db8:1::2"), NoSuchAddress);
-
- ASSERT_NO_THROW(cfg.use("*"));
- ASSERT_THROW(cfg.use("*"), DuplicateIfaceName);
+ CfgIface cfg;
+ ASSERT_THROW(cfg.use(CfgIface::V4, ""), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V4, " "), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V4, "bogus"), NoSuchIface);
+
+ ASSERT_NO_THROW(cfg.use(CfgIface::V4, "eth0"));
+ ASSERT_THROW(cfg.use(CfgIface::V4, "eth0"), DuplicateIfaceName);
+
+ ASSERT_THROW(cfg.use(CfgIface::V4, "eth0/2001:db8:1::1"), InvalidIfaceName);
+
+ ASSERT_THROW(cfg.use(CfgIface::V6, "eth0/"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "/2001:db8:1::1"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "*/2001:db8:1::1"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "bogus/2001:db8:1::1"), NoSuchIface);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "eth0/fe80::3a60:77ff:fed5:cdef"),
+ InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "eth0/fe80::3a60:77ff:fed5:cdef"),
+ InvalidIfaceName);
+ ASSERT_THROW(cfg.use(CfgIface::V6, "eth0/2001:db8:1::2"), NoSuchAddress);
+
+ ASSERT_NO_THROW(cfg.use(CfgIface::V6, "*"));
+ ASSERT_THROW(cfg.use(CfgIface::V6, "*"), DuplicateIfaceName);
}
} // end of anonymous namespace
public:
CfgMgrTest() {
// make sure we start with a clean configuration
- CfgMgr::instance().deleteSubnets4();
- CfgMgr::instance().deleteSubnets6();
- CfgMgr::instance().deleteOptionDefs();
+ clear();
}
/// @brief generates interface-id option based on provided text
~CfgMgrTest() {
// clean up after the test
+ clear();
+ }
+
+ void clear() {
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().deleteOptionDefs();
+ CfgMgr::instance().clear();
}
/// used in client classification (or just empty container for other tests)
}
+// This test verifies that the configuration staging and commit works
+// as expected.
+TEST_F(CfgMgrTest, staging) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Initially, the current configuration is a default one. We are going
+ // to get the current configuration a couple of times and make sure
+ // that always the same instance is returned.
+ ConstConfigurationPtr const_config;
+ for (int i = 0; i < 5; ++i) {
+ const_config = cfg_mgr.getCurrent();
+ ASSERT_TRUE(const_config) << "Returned NULL current configuration"
+ " for iteration " << i;
+ EXPECT_EQ(0, const_config->getSequence())
+ << "Returned invalid sequence number "
+ << const_config->getSequence() << " for iteration " << i;
+ }
+
+ // Try to get the new staging configuration. When getStaging() is called
+ // for the first time the new instance of the staging configuration is
+ // returned. This instance is returned for every call to getStaging()
+ // until commit is called.
+ ConfigurationPtr config;
+ for (int i = 0; i < 5; ++i) {
+ config = cfg_mgr.getStaging();
+ ASSERT_TRUE(config) << "Returned NULL staging configuration for"
+ " iteration " << i;
+ // The sequence id is 1 for staging because it is ahead of current
+ // configuration having sequence number 0.
+ EXPECT_EQ(1, config->getSequence()) << "Returned invalid sequence"
+ " number " << config->getSequence() << " for iteration " << i;
+ }
+
+ // This should change the staging configuration so as it becomes a current
+ // one.
+ CfgMgr::instance().commit();
+ const_config = cfg_mgr.getCurrent();
+ ASSERT_TRUE(const_config);
+ // Sequence id equal to 1 indicates that the current configuration points
+ // to the configuration that used to be a staging configuration previously.
+ EXPECT_EQ(1, const_config->getSequence());
+
+ // Create a new staging configuration. It should be assigned a new
+ // sequence id.
+ config = cfg_mgr.getStaging();
+ ASSERT_TRUE(config);
+ EXPECT_EQ(2, config->getSequence());
+
+ // Let's execute commit a couple of times. The first invocation to commit
+ // changes the configuration having sequence 2 to current configuration.
+ // Other commits are no-op.
+ for (int i = 0; i < 5; ++i) {
+ CfgMgr::instance().commit();
+ }
+
+ // The current configuration now have sequence number 2.
+ const_config = cfg_mgr.getCurrent();
+ ASSERT_TRUE(const_config);
+ EXPECT_EQ(2, const_config->getSequence());
+
+ // Clear configuration along with a history.
+ CfgMgr::instance().clear();
+
+ // After clearing configuration we should successfully get the
+ // new staging configuration.
+ const_config = cfg_mgr.getStaging();
+ ASSERT_TRUE(const_config);
+ EXPECT_EQ(1, const_config->getSequence());
+}
+
+
/// @todo Add unit-tests for testing:
/// - addActiveIface() with invalid interface name
/// - addActiveIface() with the same interface twice
/// @brief Resets selection of the interfaces from previous tests.
void resetIfaceCfg() {
- CfgMgr::instance().getConfiguration()->cfg_iface_.closeSockets();
- CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+ CfgMgr::instance().clear();
}
};
const std::string name = "interfaces";
+ ParserContextPtr parser_context(new ParserContext(Option::V4));
+
// Verify that parser constructor fails if parameter name isn't "interface"
- EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
+ EXPECT_THROW(InterfaceListConfigParser("bogus_name", parser_context),
+ isc::BadValue);
boost::scoped_ptr<InterfaceListConfigParser>
- parser(new InterfaceListConfigParser(name));
+ parser(new InterfaceListConfigParser(name, parser_context));
ElementPtr list_element = Element::createList();
list_element->add(Element::create("eth0"));
// Use CfgMgr instance to check if eth0 and eth1 was added, and that
// eth2 was not added.
- ConfigurationPtr cfg = CfgMgr::instance().getConfiguration();
+ ConfigurationPtr cfg = CfgMgr::instance().getStaging();
ASSERT_TRUE(cfg);
- ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
+ ASSERT_NO_THROW(cfg->getCfgIface().openSockets(CfgIface::V4, 10000));
EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
list_element->add(Element::create("*"));
// Reset parser and configuration.
- parser.reset(new InterfaceListConfigParser(name));
- cfg->cfg_iface_.closeSockets();
- cfg->cfg_iface_.reset();
+ parser.reset(new InterfaceListConfigParser(name, parser_context));
+ cfg->getCfgIface().closeSockets();
+ CfgMgr::instance().clear();
parser->build(list_element);
parser->commit();
- ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
+
+ cfg = CfgMgr::instance().getStaging();
+ ASSERT_NO_THROW(cfg->getCfgIface().openSockets(CfgIface::V4, 10000));
EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));