Commit 792c129a authored by Marcin Siodelski's avatar Marcin Siodelski

[master] Merge branch 'trac2545'

Conflicts:
	src/lib/dhcpsrv/Makefile.am
parents 3e191cf2 b85908b8
This diff is collapsed.
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -28,14 +28,8 @@ namespace dhcp {
class Dhcpv4Srv;
/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
typedef std::map<std::string, uint32_t> Uint32Storage;
/// @brief a collection of elements that store string values
typedef std::map<std::string, std::string> StringStorage;
/// An exception that is thrown if an error occurs while configuring an
/// \c Dhcpv4Srv object.
/// @c Dhcpv4Srv object.
class Dhcp4ConfigError : public isc::Exception {
public:
......@@ -48,97 +42,12 @@ public:
: isc::Exception(file, line, what) {}
};
/// @brief Base abstract class for all DHCPv4 parsers
/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
///
/// Each instance of a class derived from this class parses one specific config
/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
/// complex (e.g. a subnet). In such case, it is likely that a parser will
/// spawn child parsers to parse child elements in the configuration.
/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
class Dhcp4ConfigParser {
///
/// \name Constructors and Destructor
///
/// Note: The copy constructor and the assignment operator are
/// intentionally defined as private to make it explicit that this is a
/// pure base class.
//@{
private:
// Private construtor and assignment operator assures that nobody
// will be able to copy or assign a parser. There are no defined
// bodies for them.
Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
protected:
/// \brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class should
/// never be instantiated (except as part of a derived class).
Dhcp4ConfigParser() {}
public:
/// The destructor.
virtual ~Dhcp4ConfigParser() {}
//@}
/// \brief Prepare configuration value.
///
/// This method parses the "value part" of the configuration identifier
/// that corresponds to this derived class and prepares a new value to
/// apply to the server.
///
/// This method must validate the given value both in terms of syntax
/// and semantics of the configuration, so that the server will be
/// validly configured at the time of \c commit(). Note: the given
/// configuration value is normally syntactically validated, but the
/// \c build() implementation must also expect invalid input. If it
/// detects an error it may throw an exception of a derived class
/// of \c isc::Exception.
///
/// Preparing a configuration value will often require resource
/// allocation. If it fails, it may throw a corresponding standard
/// exception.
///
/// This method is not expected to be called more than once in the
/// life of the object. Although multiple calls are not prohibited
/// by the interface, the behavior is undefined.
///
/// \param config_value The configuration value for the identifier
/// corresponding to the derived class.
virtual void build(isc::data::ConstElementPtr config_value) = 0;
/// \brief Apply the prepared configuration value to the server.
///
/// This method is expected to be exception free, and, as a consequence,
/// it should normally not involve resource allocation.
/// Typically it would simply perform exception free assignment or swap
/// operation on the value prepared in \c build().
/// In some cases, however, it may be very difficult to meet this
/// condition in a realistic way, while the failure case should really
/// be very rare. In such a case it may throw, and, if the parser is
/// called via \c configureDhcp4Server(), the caller will convert the
/// exception as a fatal error.
///
/// This method is expected to be called after \c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
};
/// @brief a pointer to configuration parser
typedef boost::shared_ptr<Dhcp4ConfigParser> ParserPtr;
/// @brief a collection of parsers
///
/// This container is used to store pointer to parsers for a given scope.
typedef std::vector<ParserPtr> ParserCollection;
/// \brief Configure DHCPv4 server (\c Dhcpv4Srv) with a set of configuration values.
///
/// This function parses configuration information stored in \c config_set
/// and configures the \c server by applying the configuration to it.
/// This function parses configuration information stored in @c config_set
/// and configures the @c server by applying the configuration to it.
/// It provides the strong exception guarantee as long as the underlying
/// derived class implementations of \c DhcpConfigParser meet the assumption,
/// derived class implementations of @c DhcpConfigParser meet the assumption,
/// that is, it ensures that either configuration is fully applied or the
/// state of the server is intact.
///
......@@ -154,7 +63,8 @@ typedef std::vector<ParserPtr> ParserCollection;
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
/// 1 - malformed configuration (parsing failed)
/// 2 - logical error (parsing was successful, but the values are invalid)
/// 2 - commit failed (parsing was successful, but failed to store the
/// values in to server's configuration)
///
/// @param config_set a new configuration (JSON) for DHCPv4 server
/// @return answer that contains result of reconfiguration
......@@ -162,6 +72,16 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
/// @brief Returns the global uint32_t values storage.
///
/// This function must be only used by unit tests that need
/// to access uint32_t global storage to verify that the
/// Uint32Parser works as expected.
///
/// @return a reference to a global uint32 values storage.
const std::map<std::string, uint32_t>& getUint32Defaults();
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
......@@ -61,6 +61,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": False
} ]
}
},
......@@ -141,6 +146,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": False
} ]
}
} ]
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -17,9 +17,10 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <config/ccsession.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
#include <config/ccsession.h>
#include <dhcp/option4_addrlst.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
......@@ -35,12 +36,6 @@ using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::config;
namespace isc {
namespace dhcp {
extern Uint32Storage uint32_defaults;
}
}
namespace {
class Dhcp4ParserTest : public ::testing::Test {
......@@ -55,7 +50,9 @@ public:
// Checks if global parameter of name have expected_value
void checkGlobalUint32(string name, uint32_t expected_value) {
Uint32Storage::const_iterator it = uint32_defaults.find(name);
const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
std::map<std::string, uint32_t>::const_iterator it =
uint32_defaults.find(name);
if (it == uint32_defaults.end()) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
......@@ -81,7 +78,8 @@ public:
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
/// option value. These parameters are: "name", "code" and "data".
/// option value. These parameters are: "name", "code", "data" and
/// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
......@@ -96,14 +94,22 @@ public:
params["name"] = param_value;
params["code"] = "56";
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "56";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
params["name"] = "option_foo";
params["code"] = "56";
params["data"] = "AB CDEF0105";
params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
......@@ -140,6 +146,8 @@ public:
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
} else if (param.first == "csv-format") {
stream << "\"csv-format\": " << param.second;
}
}
stream <<
......@@ -393,9 +401,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// returned value must be 2 (values error)
// returned value must be 1 (values error)
// as the pool does not belong to that subnet
checkResult(status, 2);
checkResult(status, 1);
}
// Goal of this test is to verify if pools can be defined
......@@ -439,12 +447,14 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\""
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"01\""
" \"data\": \"01\","
" \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
......@@ -502,7 +512,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB\""
" \"data\": \"AB\","
" \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
......@@ -510,12 +521,14 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\""
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"01\""
" \"data\": \"01\","
" \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
......@@ -571,7 +584,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"0102030405060708090A\""
" \"data\": \"0102030405060708090A\","
" \"csv-format\": False"
" } ]"
" },"
" {"
......@@ -580,7 +594,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"FF\""
" \"data\": \"FF\","
" \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
......@@ -724,10 +739,70 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
}
// Verify that specific option object is returned for standard
// option which has dedicated option class derived from Option.
TEST_F(Dhcp4ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
params["name"] = "nis-servers";
// Option code 41 means nis-servers.
params["code"] = "41";
// Specify option values in a CSV (user friendly) format.
params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
params["csv-format"] = "True";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
ASSERT_TRUE(subnet);
const Subnet::OptionContainer& options = subnet->getOptions();
ASSERT_EQ(1, options.size());
// Get the search index. Index #1 is to search using option code.
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(DHO_NIS_SERVERS);
// Expect single option with the code equal to NIS_SERVERS option code.
ASSERT_EQ(1, std::distance(range.first, range.second));
// The actual pointer to the option is held in the option field
// in the structure returned.
OptionPtr option = range.first->option;
ASSERT_TRUE(option);
// Option object returned for here is expected to be Option6IA
// which is derived from Option. This class is dedicated to
// represent standard option IA_NA.
boost::shared_ptr<Option4AddrLst> option_addrs =
boost::dynamic_pointer_cast<Option4AddrLst>(option);
// If cast is unsuccessful than option returned was of a
// differnt type than Option6IA. This is wrong.
ASSERT_TRUE(option_addrs);
// Get addresses from the option.
Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
// Verify that the addresses have been configured correctly.
ASSERT_EQ(3, addrs.size());
EXPECT_EQ("192.0.2.10", addrs[0].toText());
EXPECT_EQ("192.0.2.1", addrs[1].toText());
EXPECT_EQ("192.0.2.3", addrs[2].toText());
}
/// This test checks if Uint32Parser can really parse the whole range
/// and properly err of out of range values. As we can't call Uint32Parser
/// directly, we are exploiting the fact that it is used to parse global
/// parameter renew-timer and the results are stored in uint32_defaults.
/// We get the uint32_defaults using a getUint32Defaults functions which
/// is defined only to access the values from this test.
TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
ConstElementPtr status;
......
This diff is collapsed.
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -28,7 +28,7 @@ namespace dhcp {
class Dhcpv6Srv;
/// An exception that is thrown if an error occurs while configuring an
/// \c Dhcpv6Srv object.
/// @c Dhcpv6Srv object.
class Dhcp6ConfigError : public isc::Exception {
public:
......@@ -41,115 +41,25 @@ public:
: isc::Exception(file, line, what) {}
};
/// @brief Base abstract class for all DHCPv6 parsers
/// @brief Configures DHCPv6 server
///
/// Each instance of a class derived from this class parses one specific config
/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
/// complex (e.g. a subnet). In such case, it is likely that a parser will
/// spawn child parsers to parse child elements in the configuration.
/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
class DhcpConfigParser {
///
/// \name Constructors and Destructor
///
/// Note: The copy constructor and the assignment operator are
/// intentionally defined as private to make it explicit that this is a
/// pure base class.
//@{
private:
DhcpConfigParser(const DhcpConfigParser& source);
DhcpConfigParser& operator=(const DhcpConfigParser& source);
protected:
/// \brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class should
/// never be instantiated (except as part of a derived class).
DhcpConfigParser() {}
public:
/// The destructor.
virtual ~DhcpConfigParser() {}
//@}
/// \brief Prepare configuration value.
///
/// This method parses the "value part" of the configuration identifier
/// that corresponds to this derived class and prepares a new value to
/// apply to the server.
///
/// This method must validate the given value both in terms of syntax
/// and semantics of the configuration, so that the server will be
/// validly configured at the time of \c commit(). Note: the given
/// configuration value is normally syntactically validated, but the
/// \c build() implementation must also expect invalid input. If it
/// detects an error it may throw an exception of a derived class
/// of \c isc::Exception.
///
/// Preparing a configuration value will often require resource
/// allocation. If it fails, it may throw a corresponding standard
/// exception.
///
/// This method is not expected to be called more than once in the
/// life of the object. Although multiple calls are not prohibited
/// by the interface, the behavior is undefined.
///
/// \param config_value The configuration value for the identifier
/// corresponding to the derived class.
virtual void build(isc::data::ConstElementPtr config_value) = 0;
/// \brief Apply the prepared configuration value to the server.
///
/// This method is expected to be exception free, and, as a consequence,
/// it should normally not involve resource allocation.
/// Typically it would simply perform exception free assignment or swap
/// operation on the value prepared in \c build().
/// In some cases, however, it may be very difficult to meet this
/// condition in a realistic way, while the failure case should really
/// be very rare. In such a case it may throw, and, if the parser is
/// called via \c configureDhcp6Server(), the caller will convert the
/// exception as a fatal error.
///
/// This method is expected to be called after \c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
};
/// @brief a pointer to configuration parser
typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
/// @brief a collection of parsers
///
/// This container is used to store pointer to parsers for a given scope.
typedef std::vector<ParserPtr> ParserCollection;
/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
///
/// This function parses configuration information stored in \c config_set
/// and configures the \c server by applying the configuration to it.
/// It provides the strong exception guarantee as long as the underlying
/// derived class implementations of \c DhcpConfigParser meet the assumption,
/// that is, it ensures that either configuration is fully applied or the
/// state of the server is intact.
/// This function is called every time a new configuration is received. The extra
/// parameter is a reference to DHCPv6 server component. It is currently not used
/// and CfgMgr::instance() is accessed instead.
///
/// If a syntax or semantics level error happens during the configuration
/// (such as malformed configuration or invalid configuration parameter),
/// this function throws an exception of class \c Dhcp6ConfigError.
/// If the given configuration requires resource allocation and it fails,
/// a corresponding standard exception will be thrown.
/// Other exceptions may also be thrown, depending on the implementation of
/// the underlying derived class of \c Dhcp6ConfigError.
/// In any case the strong guarantee is provided as described above except
/// in the very rare cases where the \c commit() method of a parser throws
/// an exception. If that happens this function converts the exception
/// into a \c FatalError exception and rethrows it. This exception is
/// expected to be caught at the highest level of the application to terminate
/// the program gracefully.
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
/// 1 - malformed configuration (parsing failed)
/// 2 - commit failed (parsing was successful, but the values could not be
/// stored in the configuration).
///
/// \param server The \c Dhcpv6Srv object to be configured.
/// \param config_set A JSON style configuration to apply to \c server.
/// @param server DHCPv6 server object.
/// @param config_set a new configuration for DHCPv6 server.
/// @return answer that contains result of the reconfiguration.
/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& server,
isc::data::ConstElementPtr config_set);
configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
......@@ -35,19 +35,19 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
unsigned 32 bit integer), but some are very specialized (e.g. \ref
isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
some cases, e.g. subnet6 definitions, the configuration entry is not a simple
value, but a map or a list itself. In such case, the parser iterates over all
elements and creates parsers for a given scope. This process may be repeated
(sort of) recursively.
are generic (e.g. Uint32Parser that is able to handle any
unsigned 32 bit integer), but some are very specialized (e.g.
Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
e.g. subnet6 definitions, the configuration entry is not a simple value, but
a map or a list itself. In such case, the parser iterates over all elements
and creates parsers for a given scope. This process may be repeated (sort of)
recursively.
@section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
......@@ -55,16 +55,16 @@
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
isc::dhcp::StringParser) implement that inheritance. By default, they store
values in global uint32_defaults and string_defaults storages. However, it is
possible to instruct them to store parsed values in more specific
storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
that has its own storage that is unique for each subnet. Finally, during commit
phase (commit() method), appropriate parsers can use apply parameter inheritance.
defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
inheritance. By default, they store values in global uint32_defaults and
string_defaults storages. However, it is possible to instruct them to store
parsed values in more specific storages. That capability is used, e.g. in
Subnet6ConfigParser that has its own storage that is unique for each subnet.
Finally, during commit phase (commit() method), appropriate parsers can use
apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
class called DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
......
......@@ -67,6 +67,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": False
} ]
}
},
......@@ -152,6 +157,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": False
} ]
}
} ]
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")