namespace isc {
namespace dhcp {
-/// @brief an auxiliary type used for storing an element name and its parser
typedef map<string, ConstElementPtr> ConfigPairMap;
typedef pair<string, ConstElementPtr> ConfigPair;
+typedef map<string, string> StringPairMap;
+typedef pair<string, string> StringPair;
// Parse the configuration and check that the various keywords are consistent.
void
DbAccessParser::build(isc::data::ConstElementPtr config_value) {
const ConfigPairMap& config_map = config_value->mapValue();
- // Check if the "type" keyword exists and thrown an exception if not.
- ConfigPairMap::const_iterator type_ptr = config_map.find("type");
- if (type_ptr == config_map.end()) {
+ // To cope with incremental updates, the strategy is:
+ // 1. Take a copy of the stored keyword/value pairs.
+ // 2. Update the copy with the passed keywords.
+ // 3. Perform validation checks on the updated keyword/value pairs.
+ // 4. If all is OK, update the stored keyword/value pairs.
+ // 5. Construct the updated database access string.
+
+ // 1. Take a copy of the stored keyword/value pairs.
+ map<string, string> values_copy = values_;
+
+ // 2. Update the copy with the passed keywords.
+ BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
+ values_copy[param.first] = param.second->stringValue();
+ }
+
+ // 3. Perform validation checks on the updated set of keyword/values.
+ //
+ // a. Check if the "type" keyword exists and thrown an exception if not.
+ StringPairMap::const_iterator type_ptr = values_copy.find("type");
+ if (type_ptr == values_copy.end()) {
isc_throw(TypeKeywordMissing, "lease database access parameters must "
"include the keyword 'type' to determine type of database "
"to be accessed");
}
- // Check if the 'type; keyword known and throw an exception if not.
- string dbtype = type_ptr->second->stringValue();
+ // b. Check if the 'type; keyword known and throw an exception if not.
+ string dbtype = type_ptr->second;
if ((dbtype != "memfile") && (dbtype != "mysql")) {
isc_throw(BadValue, "unknown backend database type: " << dbtype);
}
/// @todo Log a warning if the type is memfile and there are other keywords.
/// This will be done when the module is moved to libdhcpsrv
+ // 4. If all is OK, update the stored keyword/value pairs.
+ values_ = values_copy;
- // All OK, build up the access string
+ // 5. Construct the updated database access string.
dbaccess_ = "";
- BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
+ BOOST_FOREACH(StringPair keyval, values_) {
if (! dbaccess_.empty()) {
dbaccess_ += std::string(" ");
}
- dbaccess_ += (param.first + std::string("=") +
- param.second->stringValue());
+ dbaccess_ += (keyval.first + std::string("=") + keyval.second);
}
}
/// @brief Default constructor
///
- DbAccessParser()
+ /// @param param_name Name of the configuration parameter being parsed.
+ DbAccessParser(const std::string& param_name)
+ : dbaccess_(), param_name_(param_name), values_()
{}
/// The destructor.
/// @param config_value The configuration value for the "lease-database"
/// identifier.
///
+ /// @throw isc::BadValue The 'type' keyword contains an unknown database
+ /// type.
/// @throw isc::dhcp::MissingTypeKeyword The 'type' keyword is missing from
/// the list of database access keywords.
virtual void build(isc::data::ConstElementPtr config_value);
/// The result is undefined otherwise.
virtual void commit();
+ /// @brief Factory method to create parser
+ ///
+ /// Creates an instance of this parser.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new DbAccessParser(param_name));
+ }
+
/// @brief Get database access string
///
/// Used in testing to check that the configuration information has been
private:
std::string dbaccess_; ///< Database access string
+ std::string param_name_; ///< Parameter name
+ std::map<std::string, std::string> values_;
+ ///< Stored parameter values
};
}; // namespace dhcp
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- DbAccessParser parser;
+ DbAccessParser parser("lease-database");
EXPECT_NO_THROW(parser.build(json_elements));
string dbaccess = parser.getDbAccessString();
-
checkAccessString("Valid memfile", dbaccess, config);
}
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- DbAccessParser parser;
+ DbAccessParser parser("lease-database");
EXPECT_NO_THROW(parser.build(json_elements));
string dbaccess = parser.getDbAccessString();
-
checkAccessString("Valid mysql", dbaccess, config);
}
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- DbAccessParser parser;
+ DbAccessParser parser("lease-database");
EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
}
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- DbAccessParser parser;
+ DbAccessParser parser("lease-database");
+ EXPECT_THROW(parser.build(json_elements), BadValue);
+}
+
+// Check that the factory function works.
+TEST_F(DbAccessParserTest, factory) {
+ const char* config[] = {"type", "memfile",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ // Check that the parser is built through the factory.
+ boost::scoped_ptr<DhcpConfigParser> parser(
+ DbAccessParser::factory("lease-database"));
+ EXPECT_NO_THROW(parser->build(json_elements));
+
+ // Access the "raw" parser.
+ DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
+ EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
+ string dbaccess = dbap->getDbAccessString();
+ checkAccessString("Valid mysql", dbaccess, config);
+}
+
+// Check reconfiguration. Checks that incremental changes applied to the
+// database configuration are incremental.
+TEST_F(DbAccessParserTest, incrementalChanges) {
+ const char* config1[] = {"type", "memfile",
+ NULL};
+
+ // Applying config2 will cause a wholesale change.
+ const char* config2[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ // Applying incremental2 should cause a change to config3.
+ const char* incremental2[] = {"user", "me",
+ "password", "meagain",
+ NULL};
+ const char* config3[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "me",
+ "password", "meagain",
+ "name", "keatest",
+ NULL};
+
+ // incremental3 will cause an exception. There should be no change
+ // to the returned value.
+ const char* incremental3[] = {"type", "invalid",
+ "user", "you",
+ "password", "youagain",
+ NULL};
+
+ // incremental4 is a compatible change and should cause a transition
+ // to config4.
+ const char* incremental4[] = {"user", "them",
+ "password", "themagain",
+ NULL};
+ const char* config4[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "them",
+ "password", "themagain",
+ "name", "keatest",
+ NULL};
+
+ DbAccessParser parser("lease-database");
+
+ // First configuration string should cause a representation of that string
+ // to be held.
+ string json_config = toJson(config1);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ string dbaccess = parser.getDbAccessString();
+ checkAccessString("Initial configuration", dbaccess, config1);
+
+ // Applying a wholesale change will cause the access string to change
+ // to a representation of the new configuration.
+ json_config = toJson(config2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ dbaccess = parser.getDbAccessString();
+ checkAccessString("Subsequent configuration", dbaccess, config2);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ dbaccess = parser.getDbAccessString();
+ checkAccessString("Incremental configuration", dbaccess, config3);
+
+ // Applying the next incremental change should cause an exception to be
+ // thrown and there be no change to the access string.
+ json_config = toJson(incremental3);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
EXPECT_THROW(parser.build(json_elements), BadValue);
+ dbaccess = parser.getDbAccessString();
+ checkAccessString("Incompatible incremental change", dbaccess, config3);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental4);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ dbaccess = parser.getDbAccessString();
+ checkAccessString("Compatible incremental change", dbaccess, config4);
}
}; // Anonymous namespace