<< config << std::endl
<< " and the following error message was returned:"
<< ex.what() << std::endl;
+ return (false);
}
- // status object must not be NULL
+ // The status object must not be NULL
if (!status) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " NULL pointer" << std::endl;
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned a null pointer.";
+ return (false);
}
+
+ // Store the answer if we need it.
+
+ // Returned value should be 0 (configuration success)
comment_ = parseAnswer(rcode_, status);
- // returned value should be 0 (configuration success)
if (rcode_ != 0) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " error code " << rcode_ << std::endl;
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned error code "
+ << rcode_ << reason;
+ return (false);
}
- string config = "{ \"interface\": [ \"all\" ],"
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by removing all subnets
+ /// option-data, and hooks libraries. The reset must be performed after each
+ /// test to make sure that contents of the database do not affect the
+ /// results of subsequent tests.
+ void resetConfiguration() {
++ string config = "{ \"interfaces\": [ \"all\" ],"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ static_cast<void>(executeConfiguration(config,
+ "reset configuration database"));
+ // The default setting is to listen on all interfaces. In order to
+ // 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().deleteActiveIfaces();
}
/// @brief Test invalid option parameter value.
EXPECT_FALSE(desc.option->getOption(112));
}
- "{ \"interface\": [ \"all\" ],"
+// Tests of the hooks libraries configuration.
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
+ const string quote("\"");
+
+ // Create the first part of the configuration string.
+ string config =
- "loading two valid libraries"));
++ "{ \"interfaces\": [ \"all\" ],"
+ "\"hooks-libraries\": [";
+
+ // Append the libraries (separated by commas if needed)
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (i > 0) {
+ config += string(", ");
+ }
+ config += (quote + libraries[i] + quote);
+ }
+
+ // Append the remainder of the configuration.
+ config += string(
+ "],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}");
+
+ return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+ const char* library2 = NULL) {
+ std::vector<std::string> libraries;
+ if (library1 != NULL) {
+ libraries.push_back(string(library1));
+ if (library2 != NULL) {
+ libraries.push_back(string(library2));
+ }
+ }
+ return (buildHooksLibrariesConfig(libraries));
+}
+
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp6ParserTest, NoHooksLibraries) {
+// std::vector<std::string> libraries = HooksManager::getLibraryNames();
+// ASSERT_TRUE(libraries.empty());
+
+ // Parse a configuration containing no names.
+ string config = buildHooksLibrariesConfig();
+ if (!executeConfiguration(config,
+ "set configuration with no hooks libraries")) {
+ return;
+ }
+// libraries = HooksManager::getLibraryNames();
+// ASSERT_TRUE(libraries.empty());
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp6ParserTest, InvalidLibrary) {
+// std::vector<std::string> libraries = HooksManager::getLibraryNames();
+// ASSERT_TRUE(libraries.empty());
+
+ // Parse a configuration containing a failing library.
+ string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+ ConstElementPtr status;
+ ElementPtr json = Element::fromJSON(config);
+ ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_FALSE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+ std::cerr << "Reason for success: " << comment_;
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+// std::vector<std::string> libraries = HooksManager::getLibraryNames();
+// ASSERT_TRUE(libraries.empty());
+
+ // Marker files should not be present.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, NULL));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, NULL));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+ ASSERT_TRUE(executeConfiguration(config,
++ "load two valid libraries"));
+
+ // Expect two libraries to be loaded in the correct order (load marker file
+ // is present, no unload marker file).
+// std::vector<std::string> libraries = HooksManager::getLibraryNames();
+// ASSERT_EQ(2, libraries.size());
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, NULL));
+
+ // Unload the libraries. The load file should not have changed, but
+ // the unload one should indicate the unload() functions have been run.
+ config = buildHooksLibrariesConfig();
+ ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+ // Expect the hooks system to say that none are loaded.
+ // libraries = HooksManager::getLibraryNames();
+ // EXPECT_TRUE(libraries.empty());
+
+ }
+// libraries = HooksManager::getLibraryNames();
+// ASSERT_TRUE(libraries.empty());
+
+
+ // This test verifies that it is possible to select subset of interfaces on
+ // which server should listen.
+ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+
+ // Make sure there is no garbage interface configuration in the CfgMgr.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ ConstElementPtr status;
+
+ string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // 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 to listen on
+ // all interfaces.
+ TEST_F(Dhcp6ParserTest, allInterfaces) {
+
+ // Make sure there is no garbage interface configuration in the CfgMgr.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ ConstElementPtr status;
+
+ // This configuration specifies two interfaces on which server should listen
+ // bu 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\", \"eth1\", \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // All interfaces should be now active.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+ }
};
}
}
- 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);
}
+// ******************** HooksLibrariesParser *************************
+
+HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
+ : libraries_(), changed_(false)
+{
+ // Sanity check on the name.
+ if (param_name != "hooks-libraries") {
+ isc_throw(BadValue, "Internal error. Hooks libraries "
+ "parser called for the wrong parameter: " << param_name);
+ }
+}
+
+void
+HooksLibrariesParser::build(ConstElementPtr value) {
+ // Initialize.
+ libraries_.clear();
+ changed_ = false;
+
+ // Extract the list of libraries.
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ string libname = iface->str();
+ boost::erase_all(libname, "\"");
+ libraries_.push_back(libname);
+ }
+
+ // Check if the list of libraries has changed. If not, nothing is done
+ // - the command "DhcpN libreload" is required to reload the same
+ // libraries (this prevents needless reloads when anything else in the
+ // configuration is changed).
+/*
+ vector<string> current_libraries = HooksManager::getLibraryNames();
+ if (current_libraries == libraries_) {
+ return;
+ }
+
+ // Library list has changed, validate each of the libraries specified.
+ string error_libs = HooksManager::validateLibraries(libraries_);
+ if (!error_libs.empty()) {
+ isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
+ "library or libraries in error are: " + error_libs);
+ }
+*/
+ // The library list has changed and the libraries are valid, so flag for
+ // update when commit() is called.
+ changed_ = true;
+}
+
+void
+HooksLibrariesParser::commit() {
+ /// Commits the list of libraries to the configuration manager storage if
+ /// the list of libraries has changed.
+ if (changed_) {
+ HooksManager::loadLibraries(libraries_);
+ }
+}
+
+// Method for testing
+void
+HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
+ bool& changed) {
+ libraries = libraries_;
+ changed = changed_;
+}
+
// **************************** OptionDataParser *************************
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
/// @brief Shared pointer to option storage.
typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+
/// @brief A template class that stores named elements of a given data type.
///
- /// This template class is provides data value storage for configuration
- /// parameters of a given data type. The values are stored by parameter name
- /// and as instances of type "ValueType".
+ /// This template class is provides data value storage for configuration parameters
+ /// of a given data type. The values are stored by parameter name and as instances
+ /// of type "ValueType".
///
/// @param ValueType is the data type of the elements to store.
template<typename ValueType>
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_;
};
+/// @brief parser for hooks library list
+///
+/// This parser handles the list of hooks libraries. This is an optional list,
+/// which may be empty.
+///
+/// However, the parser does more than just check the list of library names.
+/// It does two other things:
+///
+/// -# The problem faced with the hooks libraries is that we wish to avoid
+/// reloading the libraries if they have not changed. (This would cause the
+/// "unload" and "load" methods to run. Although libraries should be written
+/// to cope with this, it is feasible that such an action may be constly in
+/// terms of time and resources, or may cause side effects such as clearning
+/// an internal cache.) To this end, the parser also checks the list against
+/// the list of libraries current loaded and notes if there are changes.
+/// -# If there are, the parser validates the libraries; it opens them and
+/// checks that the "version" function exists and returns the correct value.
+///
+/// Only if the library list has changed and the libraries are valid will the
+/// change be applied.
+class HooksLibrariesParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "hooks_libraries" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed.
+ ///
+ /// @throw BadValue if supplied parameter name is not "hooks_libraries"
+ HooksLibrariesParser(const std::string& param_name);
+
+ /// @brief Parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the hooks libraries list. The method also checks whether the
+ /// list of libraries is the same as that already loaded. If not, it
+ /// checks each of the libraries in the list for validity (they exist and
+ /// have a "version" function that returns the correct value).
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(isc::data::ConstElementPtr value);
+
+ /// @brief Commits hooks libraries data
+ ///
+ /// Providing that the specified libraries are valid and are different
+ /// to those already loaded, this method loads the new set of libraries
+ /// (and unloads the existing set).
+ virtual void commit();
+
+ /// @brief Returns list of parsed libraries
+ ///
+ /// Principally for testing, this returns the list of libraries as well as
+ /// an indication as to whether the list is different from the list of
+ /// libraries already loaded.
+ ///
+ /// @param libraries (out) List of libraries that were specified in the
+ /// new configuration.
+ /// @param changed (out) true if the list is different from that currently
+ /// loaded.
+ void getLibraries(std::vector<std::string>& libraries, bool& changed);
+
+private:
+ /// List of hooks libraries.
+ std::vector<std::string> libraries_;
+
+ /// Indicator flagging that the list of libraries has changed.
+ bool changed_;
+};
/// @brief Parser for option data value.
///