(config_id.compare("rebind-timer") == 0)) {
parser = new Uint32Parser(config_id,
globalContext()->uint32_values_);
- } else if (config_id.compare("interface") == 0) {
+ } else if (config_id.compare("interfaces") == 0) {
parser = new InterfaceListConfigParser(config_id);
} else if (config_id.compare("subnet4") == 0) {
parser = new Subnets4ListConfigParser(config_id);
ParserCollection independent_parsers;
ParserPtr subnet_parser;
ParserPtr option_parser;
+ ParserPtr iface_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
subnet_parser = parser;
} else if (config_pair.first == "option-data") {
option_parser = parser;
+ } else if (config_pair.first == "interfaces") {
+ // The interface parser is independent from any other
+ // parser and can be run here before any other parsers.
+ iface_parser = parser;
+ parser->build(config_pair.second);
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
if (subnet_parser) {
subnet_parser->commit();
}
+
+ if (iface_parser) {
+ iface_parser->commit();
+ }
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
"module_name": "Dhcp4",
"module_description": "DHCPv4 server daemon",
"config_data": [
- { "item_name": "interface",
+ { "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
"item_default": [ "all" ],
// deal with sockets here, just check if configuration handling
// is sane.
srv_.reset(new Dhcpv4Srv(0));
+ CfgMgr::instance().deleteActiveIfaces();
}
// Checks if global parameter of name have expected_value
/// describing an option.
std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream;
- stream << "{ \"interface\": [ \"all\" ],"
+ stream << "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
void resetConfiguration() {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
- Element::fromJSON("{ \"interface\": [ \"all\" ],"
+ Element::fromJSON("{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ ], "
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
// configuration does not include options configuration.
TEST_F(Dhcp4ParserTest, optionDataDefaults) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
// The definition is not required for the option that
// belongs to the 'dhcp4' option space as it is the
// standard option.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
// at the very end (when all other parameters are configured).
// Starting stage 1. Configure sub-options and their definitions.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
// the configuration from the stage 2 is repeated because BIND
// configuration manager sends whole configuration for the lists
// where at least one element is being modified or added.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
// option setting.
TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
// for multiple subnets.
TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
// In the first stahe we create definitions of suboptions
// that we will add to the base option.
// Let's create some dummy options: foo and foo2.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
// We add our dummy options to this option space and thus
// they should be included as sub-options in the 'vendor-opts'
// option.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
EXPECT_FALSE(desc.option->getOption(3));
}
+// This test verifies that it is possible to select subset of interfaces
+// on which server should listen.
+TEST_F(Dhcp4ParserTest, selectedInterfaces) {
+ ConstElementPtr x;
+ string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
-};
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+
+ // Make sure the config manager is clean and there is no hanging
+ // interface configuration.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // eth0 and eth1 were explicitly selected. eth2 was not.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to configure the server in such a way
+// that it listens on all interfaces.
+TEST_F(Dhcp4ParserTest, allInterfaces) {
+ ConstElementPtr x;
+ // This configuration specifies two interfaces on which server should listen
+ // but it also includes keyword 'all'. 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\",\"all\",\"eth1\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+
+ // Make sure there is no old configuration.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // All interfaces should be now active.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+
+
+}
"module_name": "Dhcp6",
"module_description": "DHCPv6 server daemon",
"config_data": [
- { "item_name": "interface",
+ { "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
"item_default": [ "all" ],
return (datadir_);
}
+void
+CfgMgr::addActiveIface(const std::string& iface) {
+ if (isIfaceListedActive(iface)) {
+ isc_throw(DuplicateListeningIface,
+ "attempt to add duplicate interface '" << iface << "'"
+ " to the set of interfaces on which server listens");
+ }
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
+ .arg(iface);
+ active_ifaces_.push_back(iface);
+}
+
+void
+CfgMgr::activateAllIfaces() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
+ all_ifaces_active_ = true;
+}
+
+void
+CfgMgr::deleteActiveIfaces() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
+ active_ifaces_.clear();
+ all_ifaces_active_ = false;
+}
+
+bool
+CfgMgr::isActiveIface(const std::string& iface) const {
+
+ // @todo Verify that the interface with the specified name is
+ // present in the system.
+
+ // If all interfaces are marked active, there is no need to check that
+ // the name of this interface has been explicitly listed.
+ if (all_ifaces_active_) {
+ return (true);
+ }
+ return (isIfaceListedActive(iface));
+}
+
+bool
+CfgMgr::isIfaceListedActive(const std::string& iface) const {
+ for (ActiveIfacesCollection::const_iterator it = active_ifaces_.begin();
+ it != active_ifaces_.end(); ++it) {
+ if (iface == *it) {
+ return (true);
+ }
+ }
+ return (false);
+}
CfgMgr::CfgMgr()
- :datadir_(DHCP_DATA_DIR) {
+ : datadir_(DHCP_DATA_DIR),
+ all_ifaces_active_(false) {
// 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
#include <map>
#include <string>
#include <vector>
+#include <list>
namespace isc {
namespace dhcp {
+/// @brief Exception thrown when the same interface has been specified twice.
+///
+/// In particular, this exception is thrown when adding interface to the set
+/// of interfaces on which server is supposed to listen.
+class DuplicateListeningIface : public Exception {
+public:
+ DuplicateListeningIface(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Configuration Manager
///
/// @return data directory
std::string getDataDir();
+ /// @brief Adds the name of the interface to the set of interfaces on which
+ /// server should listen.
+ ///
+ /// @param iface A name of the interface being added to the listening set.
+ void addActiveIface(const std::string& iface);
+
+ /// @brief Configures the server to listen on all interfaces.
+ ///
+ /// This function configrues the server to listen on all available
+ /// interfaces regardless of the interfaces specified with
+ /// @c CfgMgr::addListeningInterface.
+ void activateAllIfaces();
+
+ /// @brief Clear the collection of the interfaces that server is configured
+ /// to use to listen for incoming requests.
+ ///
+ /// Apart from clearing the list of interfaces specified with
+ /// @c CfgMgr::addListeningInterface, it also disables listening on all
+ /// interfaces if it has been enabled using @c CfgMgr::listenAllInterfaces.
+ void deleteActiveIfaces();
+
+ /// @brief Check if server is configured to listen on the interface which
+ /// name is specified as an argument to this function.
+ ///
+ /// @param iface A name of the interface to be checked.
+ ///
+ /// @return true if the specified interface belongs to the set of the
+ /// interfaces on which server is configured to listen.
+ bool isActiveIface(const std::string& iface) const;
+
protected:
/// @brief Protected constructor.
private:
+ /// @brief Checks if the specified interface is listed as active.
+ ///
+ /// This function searches for the specified interface name on the list of
+ /// active interfaces: @c CfgMgr::active_ifaces_. It does not take into
+ /// account @c CfgMgr::all_ifaces_active_ flag. If this flag is set to true
+ /// but the specified interface does not belong to
+ /// @c CfgMgr::active_ifaces_, it will return false.
+ ///
+ /// @param iface interface name.
+ ///
+ /// @return true if specified interface belongs to
+ /// @c CfgMgr::active_ifaces_.
+ bool isIfaceListedActive(const std::string& iface) const;
+
/// @brief A collection of option definitions.
///
/// A collection of option definitions that can be accessed
/// @brief directory where data files (e.g. server-id) are stored
std::string datadir_;
+
+ /// @name A collection of interface names on which server listens.
+ //@{
+ typedef std::list<std::string> ActiveIfacesCollection;
+ std::list<std::string> active_ifaces_;
+ //@}
+
+ /// A flag which indicates that server should listen on all available
+ /// interfaces.
+ bool all_ifaces_active_;
};
} // namespace isc::dhcp
namespace isc {
namespace dhcp {
+namespace {
+const char* ALL_IFACES_KEYWORD = "all";
+}
+
// *********************** ParserContext *************************
ParserContext::ParserContext(Option::Universe universe):
// ******************** InterfaceListConfigParser *************************
-InterfaceListConfigParser::InterfaceListConfigParser(const std::string&
- param_name) {
- if (param_name != "interface") {
+InterfaceListConfigParser::
+InterfaceListConfigParser(const std::string& param_name)
+ : activate_all_(false),
+ param_name_(param_name) {
+ if (param_name_ != "interfaces") {
isc_throw(BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
-void
+void
InterfaceListConfigParser::build(ConstElementPtr value) {
+ // First, we iterate over all specified entries and add it to the
+ // local container so as we can do some basic validation, e.g. eliminate
+ // duplicates.
BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
- interfaces_.push_back(iface->str());
+ std::string iface_name = iface->stringValue();
+ if (iface_name != ALL_IFACES_KEYWORD) {
+ // Let's eliminate duplicates. We could possibly allow duplicates,
+ // but if someone specified duplicated interface name it is likely
+ // that he mistyped the configuration. Failing here should draw his
+ // attention.
+ if (isIfaceAdded(iface_name)) {
+ isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
+ << " name '" << iface_name << "' specified in '"
+ << param_name_ << "' configuration parameter");
+ }
+ // @todo check that this interface exists in the system!
+ // The IfaceMgr exposes mechanisms to check this.
+
+ // Add the interface name if ok.
+ interfaces_.push_back(iface_name);
+
+ } else {
+ activate_all_ = true;
+
+ }
}
}
-void
+void
InterfaceListConfigParser::commit() {
- /// @todo: Implement per interface listening. Currently always listening
- /// on all interfaces.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Remove active interfaces and clear a flag which marks all interfaces
+ // active
+ cfg_mgr.deleteActiveIfaces();
+
+ if (activate_all_) {
+ // Activate all interfaces. There is not need to add their names
+ // explicitly.
+ cfg_mgr.activateAllIfaces();
+
+ } else {
+ // Explicitly add names of the interfaces which server should listen on.
+ BOOST_FOREACH(std::string iface, interfaces_) {
+ cfg_mgr.addActiveIface(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);
}
// **************************** OptionDataParser *************************
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
- : boolean_values_(new BooleanStorage()),
- string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
- options_(options), option_descriptor_(false),
+ : boolean_values_(new BooleanStorage()),
+ string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
+ options_(options), option_descriptor_(false),
global_context_(global_context) {
if (!options_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
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
- std::vector<std::string> interfaces_;
+ typedef std::list<std::string> IfaceListStorage;
+ IfaceListStorage interfaces_;
+
+ // Should server listen on all interfaces.
+ bool activate_all_;
+
+ // Parsed parameter name
+ std::string param_name_;
};
to clients that are no longer active on the network will become available
available sooner.
+% DHCPSRV_CFGMGR_ADD_IFACE adding listening interface %1
+A debug message issued when new interface is being added to the collection of
+interfaces on which server listens to DHCP messages.
+
% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
A debug message reported when the DHCP configuration manager is adding the
specified IPv4 subnet to its database.
A debug message reported when the DHCP configuration manager is adding the
specified IPv6 subnet to its database.
+% DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
+A debug message issued when server is being configured to listen on all
+interfaces.
+
+% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
+A debug message issued when configuration manager clears the internal list
+of active interfaces. This doesn't prevent the server from listening to
+the DHCP traffic through open sockets, but will rather be used by Interface
+Manager to select active interfaces when sockets are re-opened.
+
% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
A debug message noting that the DHCP configuration manager has deleted all IPv4
subnets in its database.
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().deleteOptionDefs();
+ CfgMgr::instance().deleteActiveIfaces();
}
/// @brief generates interface-id option based on provided text
// @todo decide if a duplicate vendor space is allowed.
}
+// This test verifies that it is possible to specify interfaces that server
+// should listen on.
+TEST_F(CfgMgrTest, addActiveIface) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.addActiveIface("eth0");
+ cfg_mgr.addActiveIface("eth1");
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.deleteActiveIfaces();
+
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to set the flag which configures the
+// server to listen on all interfaces.
+TEST_F(CfgMgrTest, activateAllIfaces) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.addActiveIface("eth0");
+ cfg_mgr.addActiveIface("eth1");
+
+ ASSERT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ ASSERT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ ASSERT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.activateAllIfaces();
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.deleteActiveIfaces();
+
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
/// @brief Constructor
///
DhcpParserTest() {
+ CfgMgr::instance().deleteActiveIfaces();
}
};
///
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
-/// 2. Does not allow name other than "interface"
-///
-/// InterfaceListParser doesn't do very much, this test will need to
-/// expand once it does.
+/// 2. Does not allow name other than "interfaces"
+/// 3. Parses list of interfaces and adds them to CfgMgr
+/// 4. Parses 'all' keyword and sets a CfgMgr flag which indicates that
+/// server will listen on all interfaces.
TEST_F(DhcpParserTest, interfaceListParserTest) {
- const std::string name = "interface";
+ const std::string name = "interfaces";
// Verify that parser constructor fails if parameter name isn't "interface"
EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
- InterfaceListConfigParser parser(name);
+ boost::scoped_ptr<InterfaceListConfigParser>
+ parser(new InterfaceListConfigParser(name));
ElementPtr list_element = Element::createList();
list_element->add(Element::create("eth0"));
list_element->add(Element::create("eth1"));
+
+ // Make sure there are no interfaces added yet.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+
+ // This should parse the configuration and add eth0 and eth1 to the list
+ // of interfaces that server should listen on.
+ parser->build(list_element);
+ parser->commit();
+
+ // Use CfgMgr instance to check if eth0 and eth1 was added, and that
+ // eth2 was not added.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ // Add keyword all to the configuration. This should activate all
+ // interfaces, including eth2, even though it has not been explicitly
+ // added.
+ list_element->add(Element::create("all"));
+
+ // Reset parser's state.
+ parser.reset(new InterfaceListConfigParser(name));
+ parser->build(list_element);
+ parser->commit();
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
}
/// @brief Test Implementation of abstract OptionDataParser class. Allows
-/// testing basic option parsing.
+/// testing basic option parsing.
class UtestOptionDataParser : public OptionDataParser {
public: