mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[3360] Addressed review comments.
This commit is contained in:
@@ -1535,7 +1535,6 @@ AC_CONFIG_FILES([compatcheck/Makefile
|
|||||||
src/lib/dhcpsrv/Makefile
|
src/lib/dhcpsrv/Makefile
|
||||||
src/lib/dhcpsrv/tests/Makefile
|
src/lib/dhcpsrv/tests/Makefile
|
||||||
src/lib/dhcpsrv/tests/test_libraries.h
|
src/lib/dhcpsrv/tests/test_libraries.h
|
||||||
src/lib/dhcpsrv/tests/testdata/Makefile
|
|
||||||
src/lib/dhcp/tests/Makefile
|
src/lib/dhcp/tests/Makefile
|
||||||
src/lib/dns/benchmarks/Makefile
|
src/lib/dns/benchmarks/Makefile
|
||||||
src/lib/dns/gen-rdatacode.py
|
src/lib/dns/gen-rdatacode.py
|
||||||
|
@@ -3732,7 +3732,7 @@ Dhcp4/subnet4 [] list (default)
|
|||||||
<screen>
|
<screen>
|
||||||
> <userinput>config set Dhcp4/lease-database/type "memfile"</userinput>
|
> <userinput>config set Dhcp4/lease-database/type "memfile"</userinput>
|
||||||
> <userinput>config set Dhcp4/lease-database/persist true</userinput>
|
> <userinput>config set Dhcp4/lease-database/persist true</userinput>
|
||||||
> <userinput>config set Dhcp4/lease-database/leasefile "/tmp/kea-leases4.csv"</userinput>
|
> <userinput>config set Dhcp4/lease-database/name "/tmp/kea-leases4.csv"</userinput>
|
||||||
> <userinput>config commit</userinput>
|
> <userinput>config commit</userinput>
|
||||||
</screen>
|
</screen>
|
||||||
will change the default location of the lease file to /tmp/kea-leases4.csv.
|
will change the default location of the lease file to /tmp/kea-leases4.csv.
|
||||||
|
@@ -197,12 +197,6 @@
|
|||||||
"item_type": "boolean",
|
"item_type": "boolean",
|
||||||
"item_optional": true,
|
"item_optional": true,
|
||||||
"item_default": true
|
"item_default": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"item_name": "leasefile",
|
|
||||||
"item_type": "string",
|
|
||||||
"item_optional": true,
|
|
||||||
"item_default": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -191,12 +191,6 @@
|
|||||||
"item_type": "boolean",
|
"item_type": "boolean",
|
||||||
"item_optional": true,
|
"item_optional": true,
|
||||||
"item_default": true
|
"item_default": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"item_name": "leasefile",
|
|
||||||
"item_type": "string",
|
|
||||||
"item_optional": true,
|
|
||||||
"item_default": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -54,11 +54,18 @@ DUID::decode(const std::string& text) {
|
|||||||
/// @todo optimize stream operations here.
|
/// @todo optimize stream operations here.
|
||||||
std::vector<std::string> split_text;
|
std::vector<std::string> split_text;
|
||||||
boost::split(split_text, text, boost::is_any_of(":"),
|
boost::split(split_text, text, boost::is_any_of(":"),
|
||||||
boost::algorithm::token_compress_on);
|
boost::algorithm::token_compress_off);
|
||||||
|
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
for (size_t i = 0; i < split_text.size(); ++i) {
|
for (size_t i = 0; i < split_text.size(); ++i) {
|
||||||
if (split_text[i].size() == 1) {
|
// If there are multiple tokens and the current one is empty, it
|
||||||
|
// means that two consecutive colons were specified. This is not
|
||||||
|
// allowed for client identifier.
|
||||||
|
if ((split_text.size() > 1) && split_text[i].empty()) {
|
||||||
|
isc_throw(isc::BadValue, "invalid identifier '" << text << "': "
|
||||||
|
<< " tokens must be separated with a single colon");
|
||||||
|
|
||||||
|
} else if (split_text[i].size() == 1) {
|
||||||
s << "0";
|
s << "0";
|
||||||
|
|
||||||
} else if (split_text[i].size() > 2) {
|
} else if (split_text[i].size() > 2) {
|
||||||
|
@@ -65,15 +65,23 @@ std::string HWAddr::toText(bool include_htype) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HWAddr
|
HWAddr
|
||||||
HWAddr::fromText(const std::string& text) {
|
HWAddr::fromText(const std::string& text, const uint8_t htype) {
|
||||||
/// @todo optimize stream operations here.
|
/// @todo optimize stream operations here.
|
||||||
std::vector<std::string> split_text;
|
std::vector<std::string> split_text;
|
||||||
boost::split(split_text, text, boost::is_any_of(":"),
|
boost::split(split_text, text, boost::is_any_of(":"),
|
||||||
boost::algorithm::token_compress_on);
|
boost::algorithm::token_compress_off);
|
||||||
|
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
for (size_t i = 0; i < split_text.size(); ++i) {
|
for (size_t i = 0; i < split_text.size(); ++i) {
|
||||||
if (split_text[i].size() == 1) {
|
// If there are multiple tokens and the current one is empty, it
|
||||||
|
// means that two consecutive colons were specified. This is not
|
||||||
|
// allowed for hardware address.
|
||||||
|
if ((split_text.size() > 1) && split_text[i].empty()) {
|
||||||
|
isc_throw(isc::BadValue, "failed to create hardware address"
|
||||||
|
" from text '" << text << "': tokens of the hardware"
|
||||||
|
" address must be separated with a single colon");
|
||||||
|
|
||||||
|
} else if (split_text[i].size() == 1) {
|
||||||
s << "0";
|
s << "0";
|
||||||
|
|
||||||
} else if (split_text[i].size() > 2) {
|
} else if (split_text[i].size() > 2) {
|
||||||
@@ -89,7 +97,7 @@ HWAddr::fromText(const std::string& text) {
|
|||||||
isc_throw(isc::BadValue, "failed to create hwaddr from text '"
|
isc_throw(isc::BadValue, "failed to create hwaddr from text '"
|
||||||
<< text << "': " << ex.what());
|
<< text << "': " << ex.what());
|
||||||
}
|
}
|
||||||
return (HWAddr(binary, HTYPE_ETHER));
|
return (HWAddr(binary, htype));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HWAddr::operator==(const HWAddr& other) const {
|
bool HWAddr::operator==(const HWAddr& other) const {
|
||||||
|
@@ -79,9 +79,11 @@ public:
|
|||||||
/// type.
|
/// type.
|
||||||
///
|
///
|
||||||
/// @param text HW address in the textual format.
|
/// @param text HW address in the textual format.
|
||||||
|
/// @param htype Hardware type.
|
||||||
///
|
///
|
||||||
/// @return Instance of the HW address created from text.
|
/// @return Instance of the HW address created from text.
|
||||||
static HWAddr fromText(const std::string& text);
|
static HWAddr fromText(const std::string& text,
|
||||||
|
const uint8_t htype = HTYPE_ETHER);
|
||||||
|
|
||||||
/// @brief Compares two hardware addresses for equality
|
/// @brief Compares two hardware addresses for equality
|
||||||
bool operator==(const HWAddr& other) const;
|
bool operator==(const HWAddr& other) const;
|
||||||
|
@@ -145,11 +145,11 @@ TEST(DuidTest, fromText) {
|
|||||||
duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
|
duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
|
||||||
);
|
);
|
||||||
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
|
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
|
||||||
// Repeated colon sign in the DUID should be ignored.
|
// Repeated colon sign is not allowed.
|
||||||
ASSERT_NO_THROW(
|
EXPECT_THROW(
|
||||||
duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab")))
|
duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))),
|
||||||
|
isc::BadValue
|
||||||
);
|
);
|
||||||
EXPECT_EQ("00:bb:0d:ee:ef:ab", duid->toText());
|
|
||||||
// DUID with excessive number of digits for one of the bytes.
|
// DUID with excessive number of digits for one of the bytes.
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
|
duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
|
||||||
@@ -299,15 +299,16 @@ TEST(ClientIdTest, fromText) {
|
|||||||
cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
|
cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
|
||||||
);
|
);
|
||||||
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
|
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
|
||||||
// Repeated colon sign in the ClientId should be ignored.
|
// Repeated colon sign in the ClientId is not allowed.
|
||||||
ASSERT_NO_THROW(
|
EXPECT_THROW(
|
||||||
cid = ClientId::fromText("00::bb:D:ee:EF:ab")
|
ClientId::fromText("00::bb:D:ee:EF:ab"),
|
||||||
|
isc::BadValue
|
||||||
|
|
||||||
);
|
);
|
||||||
EXPECT_EQ("00:bb:0d:ee:ef:ab", cid->toText());
|
|
||||||
// ClientId with excessive number of digits for one of the bytes.
|
// ClientId with excessive number of digits for one of the bytes.
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
cid = ClientId::fromText("00:01:021:03:04:05:06"),
|
ClientId::fromText("00:01:021:03:04:05:06"),
|
||||||
isc::BadValue
|
isc::BadValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -149,6 +149,12 @@ TEST(HWAddrTest, fromText) {
|
|||||||
);
|
);
|
||||||
EXPECT_TRUE(hwaddr->toText(false).empty());
|
EXPECT_TRUE(hwaddr->toText(false).empty());
|
||||||
|
|
||||||
|
// HWAddr should not allow multiple consecutive colons.
|
||||||
|
EXPECT_THROW(
|
||||||
|
hwaddr.reset(new HWAddr(HWAddr::fromText("00::01:00:bc:0d:67"))),
|
||||||
|
isc::BadValue
|
||||||
|
);
|
||||||
|
|
||||||
// There should be no more than two digits per byte of the HW addr.
|
// There should be no more than two digits per byte of the HW addr.
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
|
hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
|
||||||
|
@@ -33,6 +33,8 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
|
|||||||
|
|
||||||
# Make sure the generated files are deleted in a "clean" operation
|
# Make sure the generated files are deleted in a "clean" operation
|
||||||
CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc s-messages
|
CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc s-messages
|
||||||
|
# Remove CSV files created by the CSVLeaseFile6 and CSVLeaseFile4 unit tests.
|
||||||
|
CLEANFILES += *.csv
|
||||||
|
|
||||||
lib_LTLIBRARIES = libb10-dhcpsrv.la
|
lib_LTLIBRARIES = libb10-dhcpsrv.la
|
||||||
libb10_dhcpsrv_la_SOURCES =
|
libb10_dhcpsrv_la_SOURCES =
|
||||||
|
@@ -46,20 +46,20 @@ CSVLeaseFile4::append(const Lease4& lease) const {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
CSVLeaseFile4::next(Lease4Ptr& lease) {
|
CSVLeaseFile4::next(Lease4Ptr& lease) {
|
||||||
// We will return NULL pointer if the lease is not read.
|
// Read the CSV row and try to create a lease from the values read.
|
||||||
lease.reset();
|
// This may easily result in exception. We don't want this function
|
||||||
// Get the row of CSV values.
|
// to throw exceptions, so we catch them all and rather return the
|
||||||
CSVRow row;
|
// false value.
|
||||||
CSVFile::next(row);
|
|
||||||
// The empty row signals EOF.
|
|
||||||
if (row == CSVFile::EMPTY_ROW()) {
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create a lease from the values read. This may easily result in
|
|
||||||
// exception. We don't want this function to throw exceptions, so we catch
|
|
||||||
// them all and rather return the false value.
|
|
||||||
try {
|
try {
|
||||||
|
// Get the row of CSV values.
|
||||||
|
CSVRow row;
|
||||||
|
CSVFile::next(row);
|
||||||
|
// The empty row signals EOF.
|
||||||
|
if (row == CSVFile::EMPTY_ROW()) {
|
||||||
|
lease.reset();
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
// Get client id. It is possible that the client id is empty and the
|
// Get client id. It is possible that the client id is empty and the
|
||||||
// returned pointer is NULL. This is ok, but if the client id is NULL,
|
// returned pointer is NULL. This is ok, but if the client id is NULL,
|
||||||
// we need to be careful to not use the NULL pointer.
|
// we need to be careful to not use the NULL pointer.
|
||||||
@@ -68,7 +68,7 @@ CSVLeaseFile4::next(Lease4Ptr& lease) {
|
|||||||
if (client_id) {
|
if (client_id) {
|
||||||
client_id_vec = client_id->getClientId();
|
client_id_vec = client_id->getClientId();
|
||||||
}
|
}
|
||||||
size_t client_id_len = client_id_vec.empty() ? 0 : client_id_vec.size();
|
size_t client_id_len = client_id_vec.size();
|
||||||
|
|
||||||
// Get the HW address. It should never be empty and the readHWAddr checks
|
// Get the HW address. It should never be empty and the readHWAddr checks
|
||||||
// that.
|
// that.
|
||||||
|
@@ -46,20 +46,20 @@ CSVLeaseFile6::append(const Lease6& lease) const {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
CSVLeaseFile6::next(Lease6Ptr& lease) {
|
CSVLeaseFile6::next(Lease6Ptr& lease) {
|
||||||
// We will return NULL pointer if the lease is not read.
|
// Read the CSV row and try to create a lease from the values read.
|
||||||
lease.reset();
|
// This may easily result in exception. We don't want this function
|
||||||
// Get the row of CSV values.
|
// to throw exceptions, so we catch them all and rather return the
|
||||||
CSVRow row;
|
// false value.
|
||||||
CSVFile::next(row);
|
|
||||||
// The empty row signals EOF.
|
|
||||||
if (row == CSVFile::EMPTY_ROW()) {
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create a lease from the values read. This may easily result in
|
|
||||||
// exception. We don't want this function to throw exceptions, so we catch
|
|
||||||
// them all and rather return the false value.
|
|
||||||
try {
|
try {
|
||||||
|
// Get the row of CSV values.
|
||||||
|
CSVRow row;
|
||||||
|
CSVFile::next(row);
|
||||||
|
// The empty row signals EOF.
|
||||||
|
if (row == CSVFile::EMPTY_ROW()) {
|
||||||
|
lease.reset();
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
lease.reset(new Lease6(readType(row), readAddress(row), readDUID(row),
|
lease.reset(new Lease6(readType(row), readAddress(row), readDUID(row),
|
||||||
readIAID(row), readPreferred(row),
|
readIAID(row), readPreferred(row),
|
||||||
readValid(row), 0, 0, // t1, t2 = 0
|
readValid(row), 0, 0, // t1, t2 = 0
|
||||||
|
@@ -34,7 +34,6 @@ namespace dhcp {
|
|||||||
DbAccessParser::DbAccessParser(const std::string&, const ParserContext& ctx)
|
DbAccessParser::DbAccessParser(const std::string&, const ParserContext& ctx)
|
||||||
: values_(), ctx_(ctx)
|
: values_(), ctx_(ctx)
|
||||||
{
|
{
|
||||||
ctx_ = ctx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the configuration and check that the various keywords are consistent.
|
// Parse the configuration and check that the various keywords are consistent.
|
||||||
|
@@ -447,7 +447,7 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
|
|||||||
|
|
||||||
std::string lease_file;
|
std::string lease_file;
|
||||||
try {
|
try {
|
||||||
lease_file = getParameter("leasefile");
|
lease_file = getParameter("name");
|
||||||
} catch (const Exception& ex) {
|
} catch (const Exception& ex) {
|
||||||
lease_file = getDefaultLeaseFilePath(u);
|
lease_file = getDefaultLeaseFilePath(u);
|
||||||
}
|
}
|
||||||
|
@@ -57,19 +57,15 @@ namespace dhcp {
|
|||||||
///
|
///
|
||||||
/// Originally, the Memfile backend didn't write leases to disk. This was
|
/// Originally, the Memfile backend didn't write leases to disk. This was
|
||||||
/// particularly useful for testing server performance in non-disk bound
|
/// particularly useful for testing server performance in non-disk bound
|
||||||
/// conditions. In order to preserve this capability, the new parameters
|
/// conditions. In order to preserve this capability, the new parameter
|
||||||
/// "persist4=yes|no" and "persist6=yes|no" has been introduced in the
|
/// "persist=true|false" has been introduced in the database access string.
|
||||||
/// database access string. For example, database access string:
|
/// For example, database access string: "type=memfile persist=true"
|
||||||
/// "type=memfile persist4=no persist6=yes" disables disk writes to disk
|
/// enables writes of leases to a disk.
|
||||||
/// of DHCPv4 leases enables writes to disk of DHCPv6 leases.
|
|
||||||
///
|
///
|
||||||
/// The lease file locations can be specified for DHCPv4 and DHCPv6 leases
|
/// The lease file locations can be specified with the "name=[path]"
|
||||||
/// with the following two parameters in the database access string:
|
/// parameter in the database access string. The [path] is the
|
||||||
/// - leasefile4
|
/// absolute path to the file (including file name). If this parameter
|
||||||
/// - leasefile6
|
/// is not specified, the default location in the installation
|
||||||
///
|
|
||||||
/// They specify the absolute path to the files (including file names).
|
|
||||||
/// If they are not specified, the default location in the installation
|
|
||||||
/// directory is used: var/bind10/kea-leases4.csv and
|
/// directory is used: var/bind10/kea-leases4.csv and
|
||||||
/// var/bind10/kea-leases6.csv.
|
/// var/bind10/kea-leases6.csv.
|
||||||
class Memfile_LeaseMgr : public LeaseMgr {
|
class Memfile_LeaseMgr : public LeaseMgr {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
SUBDIRS = . testdata
|
SUBDIRS = .
|
||||||
|
|
||||||
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
|
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
|
||||||
AM_CPPFLAGS += $(BOOST_INCLUDES)
|
AM_CPPFLAGS += $(BOOST_INCLUDES)
|
||||||
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests/testdata\"
|
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
|
||||||
AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests/testdata\"
|
AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
|
||||||
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
|
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
|
||||||
|
|
||||||
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||||
|
@@ -248,7 +248,7 @@ TEST_F(DbAccessParserTest, emptyKeyword) {
|
|||||||
TEST_F(DbAccessParserTest, persistV4Memfile) {
|
TEST_F(DbAccessParserTest, persistV4Memfile) {
|
||||||
const char* config[] = {"type", "memfile",
|
const char* config[] = {"type", "memfile",
|
||||||
"persist", "true",
|
"persist", "true",
|
||||||
"leasefile", "/opt/bind10/var/kea-leases4.csv",
|
"name", "/opt/bind10/var/kea-leases4.csv",
|
||||||
NULL};
|
NULL};
|
||||||
|
|
||||||
string json_config = toJson(config);
|
string json_config = toJson(config);
|
||||||
@@ -267,7 +267,7 @@ TEST_F(DbAccessParserTest, persistV4Memfile) {
|
|||||||
TEST_F(DbAccessParserTest, persistV6Memfile) {
|
TEST_F(DbAccessParserTest, persistV6Memfile) {
|
||||||
const char* config[] = {"type", "memfile",
|
const char* config[] = {"type", "memfile",
|
||||||
"persist", "true",
|
"persist", "true",
|
||||||
"leasefile", "/opt/bind10/var/kea-leases6.csv",
|
"name", "/opt/bind10/var/kea-leases6.csv",
|
||||||
NULL};
|
NULL};
|
||||||
|
|
||||||
string json_config = toJson(config);
|
string json_config = toJson(config);
|
||||||
|
@@ -673,7 +673,13 @@ GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) {
|
|||||||
|
|
||||||
// after the lease is deleted, it should really be gone
|
// after the lease is deleted, it should really be gone
|
||||||
x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
|
x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
|
||||||
EXPECT_EQ(Lease6Ptr(), x);
|
EXPECT_FALSE(x);
|
||||||
|
|
||||||
|
// Reopen the lease storage to make sure that lease is gone from the
|
||||||
|
// persistent storage.
|
||||||
|
reopen(V6);
|
||||||
|
x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
|
||||||
|
EXPECT_FALSE(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -689,7 +695,7 @@ GenericLeaseMgrTest::testBasicLease4() {
|
|||||||
lmptr_->commit();
|
lmptr_->commit();
|
||||||
|
|
||||||
// Reopen the database to ensure that they actually got stored.
|
// Reopen the database to ensure that they actually got stored.
|
||||||
reopen();
|
reopen(V4);
|
||||||
|
|
||||||
Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
|
Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
|
||||||
ASSERT_TRUE(l_returned);
|
ASSERT_TRUE(l_returned);
|
||||||
@@ -718,7 +724,7 @@ GenericLeaseMgrTest::testBasicLease4() {
|
|||||||
ASSERT_TRUE(l_returned);
|
ASSERT_TRUE(l_returned);
|
||||||
detailCompareLease(leases[2], l_returned);
|
detailCompareLease(leases[2], l_returned);
|
||||||
|
|
||||||
reopen();
|
reopen(V4);
|
||||||
|
|
||||||
// The deleted lease should be still gone after we re-read leases from
|
// The deleted lease should be still gone after we re-read leases from
|
||||||
// persistent storage.
|
// persistent storage.
|
||||||
@@ -740,7 +746,7 @@ GenericLeaseMgrTest::testBasicLease4() {
|
|||||||
|
|
||||||
ASSERT_NO_THROW(lmptr_->updateLease4(leases[2]));
|
ASSERT_NO_THROW(lmptr_->updateLease4(leases[2]));
|
||||||
|
|
||||||
reopen();
|
reopen(V4);
|
||||||
|
|
||||||
// The lease should be now updated in the storage.
|
// The lease should be now updated in the storage.
|
||||||
l_returned = lmptr_->getLease4(ioaddress4_[2]);
|
l_returned = lmptr_->getLease4(ioaddress4_[2]);
|
||||||
@@ -1420,6 +1426,87 @@ GenericLeaseMgrTest::testUpdateLease6() {
|
|||||||
EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
|
EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GenericLeaseMgrTest::testRecreateLease4() {
|
||||||
|
// Create a lease.
|
||||||
|
std::vector<Lease4Ptr> leases = createLeases4();
|
||||||
|
// Copy the lease so as we can freely modify it.
|
||||||
|
Lease4Ptr lease(new Lease4(*leases[0]));
|
||||||
|
|
||||||
|
// Add a lease.
|
||||||
|
EXPECT_TRUE(lmptr_->addLease(lease));
|
||||||
|
lmptr_->commit();
|
||||||
|
|
||||||
|
// Check that the lease has been successfuly added.
|
||||||
|
Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[0]);
|
||||||
|
ASSERT_TRUE(l_returned);
|
||||||
|
detailCompareLease(lease, l_returned);
|
||||||
|
|
||||||
|
// Delete a lease, check that it's gone.
|
||||||
|
EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[0]));
|
||||||
|
EXPECT_FALSE(lmptr_->getLease4(ioaddress4_[0]));
|
||||||
|
|
||||||
|
// Modify the copy of the lease. Increasing values or negating them ensures
|
||||||
|
// that they are really modified, because we will never get the same values.
|
||||||
|
++lease->subnet_id_;
|
||||||
|
++lease->valid_lft_;
|
||||||
|
lease->fqdn_fwd_ = !lease->fqdn_fwd_;
|
||||||
|
// Make sure that the lease has been really modified.
|
||||||
|
ASSERT_NE(*lease, *leases[1]);
|
||||||
|
// Add the updated lease.
|
||||||
|
EXPECT_TRUE(lmptr_->addLease(lease));
|
||||||
|
lmptr_->commit();
|
||||||
|
|
||||||
|
// Reopen the lease database, so as the lease is re-read.
|
||||||
|
reopen(V4);
|
||||||
|
|
||||||
|
// The lease in the database should be modified.
|
||||||
|
l_returned = lmptr_->getLease4(ioaddress4_[0]);
|
||||||
|
ASSERT_TRUE(l_returned);
|
||||||
|
detailCompareLease(lease, l_returned);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GenericLeaseMgrTest::testRecreateLease6() {
|
||||||
|
// Create a lease.
|
||||||
|
std::vector<Lease6Ptr> leases = createLeases6();
|
||||||
|
// Copy the lease so as we can freely modify it.
|
||||||
|
Lease6Ptr lease(new Lease6(*leases[0]));
|
||||||
|
|
||||||
|
// Add a lease.
|
||||||
|
EXPECT_TRUE(lmptr_->addLease(lease));
|
||||||
|
lmptr_->commit();
|
||||||
|
|
||||||
|
// Check that the lease has been successfuly added.
|
||||||
|
Lease6Ptr l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
|
||||||
|
ASSERT_TRUE(l_returned);
|
||||||
|
detailCompareLease(lease, l_returned);
|
||||||
|
|
||||||
|
// Delete a lease, check that it's gone.
|
||||||
|
EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[0]));
|
||||||
|
EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]));
|
||||||
|
|
||||||
|
// Modify the copy of the lease. Increasing values or negating them ensures
|
||||||
|
// that they are really modified, because we will never get the same values.
|
||||||
|
++lease->subnet_id_;
|
||||||
|
++lease->valid_lft_;
|
||||||
|
lease->fqdn_fwd_ = !lease->fqdn_fwd_;
|
||||||
|
// Make sure that the lease has been really modified.
|
||||||
|
ASSERT_NE(*lease, *leases[1]);
|
||||||
|
// Add the updated lease.
|
||||||
|
EXPECT_TRUE(lmptr_->addLease(lease));
|
||||||
|
lmptr_->commit();
|
||||||
|
|
||||||
|
// Reopen the lease database, so as the lease is re-read.
|
||||||
|
reopen(V6);
|
||||||
|
|
||||||
|
// The lease in the database should be modified.
|
||||||
|
l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
|
||||||
|
ASSERT_TRUE(l_returned);
|
||||||
|
detailCompareLease(lease, l_returned);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}; // namespace test
|
}; // namespace test
|
||||||
}; // namespace dhcp
|
}; // namespace dhcp
|
||||||
}; // namespace isc
|
}; // namespace isc
|
||||||
|
@@ -167,6 +167,13 @@ public:
|
|||||||
/// @todo: check if it does overlap with @ref testGetLease4NullClientId()
|
/// @todo: check if it does overlap with @ref testGetLease4NullClientId()
|
||||||
void testLease4NullClientId();
|
void testLease4NullClientId();
|
||||||
|
|
||||||
|
/// @brief Check that the DHCPv4 lease can be added, removed and recreated.
|
||||||
|
///
|
||||||
|
/// This test creates a lease, removes it and then recreates it with some
|
||||||
|
/// of the attributes changed. Next it verifies that the lease in the
|
||||||
|
/// persistent storage has been updated as expected.
|
||||||
|
void testRecreateLease4();
|
||||||
|
|
||||||
/// @brief Basic Lease6 Checks
|
/// @brief Basic Lease6 Checks
|
||||||
///
|
///
|
||||||
/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
|
/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
|
||||||
@@ -231,6 +238,13 @@ public:
|
|||||||
/// Checks that the code is able to update an IPv6 lease in the database.
|
/// Checks that the code is able to update an IPv6 lease in the database.
|
||||||
void testUpdateLease6();
|
void testUpdateLease6();
|
||||||
|
|
||||||
|
/// @brief Check that the DHCPv6 lease can be added, removed and recreated.
|
||||||
|
///
|
||||||
|
/// This test creates a lease, removes it and then recreates it with some
|
||||||
|
/// of the attributes changed. Next it verifies that the lease in the
|
||||||
|
/// persistent storage has been updated as expected.
|
||||||
|
void testRecreateLease6();
|
||||||
|
|
||||||
/// @brief String forms of IPv4 addresses
|
/// @brief String forms of IPv4 addresses
|
||||||
std::vector<std::string> straddress4_;
|
std::vector<std::string> straddress4_;
|
||||||
|
|
||||||
|
@@ -21,10 +21,20 @@ namespace isc {
|
|||||||
namespace dhcp {
|
namespace dhcp {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
|
/// @brief This class contains functions to perform IO operations on files.
|
||||||
|
///
|
||||||
|
/// This class is solely used by unit tests. Some tests often need files
|
||||||
|
/// as an input. This class allows for easy creation of text files that can
|
||||||
|
/// be later used by unit tests. It also provides method to read the contents
|
||||||
|
/// of the existing file and remove existing file (cleanup after unit test).
|
||||||
class LeaseFileIO {
|
class LeaseFileIO {
|
||||||
public:
|
public:
|
||||||
|
/// @brief Constructor
|
||||||
|
///
|
||||||
|
/// @param filename Abolsute path to the file.
|
||||||
LeaseFileIO(const std::string& filename);
|
LeaseFileIO(const std::string& filename);
|
||||||
|
|
||||||
|
/// @brief Destructor.
|
||||||
~LeaseFileIO();
|
~LeaseFileIO();
|
||||||
|
|
||||||
/// @brief Check if test file exists on disk.
|
/// @brief Check if test file exists on disk.
|
||||||
|
@@ -96,7 +96,7 @@ public:
|
|||||||
static std::string getConfigString(Universe u) {
|
static std::string getConfigString(Universe u) {
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
s << "type=memfile " << (u == V4 ? "universe=4 " : "universe=6 ")
|
s << "type=memfile " << (u == V4 ? "universe=4 " : "universe=6 ")
|
||||||
<< "leasefile="
|
<< "name="
|
||||||
<< getLeaseFilePath(u == V4 ? "leasefile4_0.csv" : "leasefile6_0.csv");
|
<< getLeaseFilePath(u == V4 ? "leasefile4_0.csv" : "leasefile6_0.csv");
|
||||||
return (s.str());
|
return (s.str());
|
||||||
}
|
}
|
||||||
@@ -134,13 +134,13 @@ TEST_F(MemfileLeaseMgrTest, constructor) {
|
|||||||
EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
|
EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
|
||||||
|
|
||||||
pmap["persist"] = "true";
|
pmap["persist"] = "true";
|
||||||
pmap["leasefile"] = getLeaseFilePath("leasefile4_1.csv");
|
pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
|
||||||
EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
|
EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
|
||||||
|
|
||||||
// Expecting that persist parameter is yes or no. Everything other than
|
// Expecting that persist parameter is yes or no. Everything other than
|
||||||
// that is wrong.
|
// that is wrong.
|
||||||
pmap["persist"] = "bogus";
|
pmap["persist"] = "bogus";
|
||||||
pmap["leasefile"] = getLeaseFilePath("leasefile4_1.csv");
|
pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
|
||||||
EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
|
EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,10 +160,10 @@ TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) {
|
|||||||
|
|
||||||
LeaseMgr::ParameterMap pmap;
|
LeaseMgr::ParameterMap pmap;
|
||||||
pmap["universe"] = "4";
|
pmap["universe"] = "4";
|
||||||
pmap["leasefile"] = getLeaseFilePath("leasefile4_1.csv");
|
pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
|
||||||
boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
|
boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
|
||||||
|
|
||||||
EXPECT_EQ(pmap["leasefile"],
|
EXPECT_EQ(pmap["name"],
|
||||||
lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4));
|
lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4));
|
||||||
|
|
||||||
pmap["persist"] = "false";
|
pmap["persist"] = "false";
|
||||||
@@ -183,7 +183,7 @@ TEST_F(MemfileLeaseMgrTest, persistLeases) {
|
|||||||
LeaseMgr::ParameterMap pmap;
|
LeaseMgr::ParameterMap pmap;
|
||||||
pmap["universe"] = "4";
|
pmap["universe"] = "4";
|
||||||
// Specify the names of the lease files. Leases will be written.
|
// Specify the names of the lease files. Leases will be written.
|
||||||
pmap["leasefile"] = getLeaseFilePath("leasefile4_1.csv");
|
pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
|
||||||
boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
|
boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
|
||||||
|
|
||||||
lease_mgr.reset(new Memfile_LeaseMgr(pmap));
|
lease_mgr.reset(new Memfile_LeaseMgr(pmap));
|
||||||
@@ -191,7 +191,7 @@ TEST_F(MemfileLeaseMgrTest, persistLeases) {
|
|||||||
EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
|
EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
|
||||||
|
|
||||||
pmap["universe"] = "6";
|
pmap["universe"] = "6";
|
||||||
pmap["leasefile"] = getLeaseFilePath("leasefile6_1.csv");
|
pmap["name"] = getLeaseFilePath("leasefile6_1.csv");
|
||||||
lease_mgr.reset(new Memfile_LeaseMgr(pmap));
|
lease_mgr.reset(new Memfile_LeaseMgr(pmap));
|
||||||
EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
|
EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
|
||||||
EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
|
EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
|
||||||
@@ -384,6 +384,25 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6) {
|
|||||||
testUpdateLease6();
|
testUpdateLease6();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief DHCPv4 Lease recreation tests
|
||||||
|
///
|
||||||
|
/// Checks that the lease can be created, deleted and recreated with
|
||||||
|
/// different parameters. It also checks that the re-created lease is
|
||||||
|
/// correctly stored in the lease database.
|
||||||
|
TEST_F(MemfileLeaseMgrTest, testRecreateLease4) {
|
||||||
|
startBackend(V4);
|
||||||
|
testRecreateLease4();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief DHCPv6 Lease recreation tests
|
||||||
|
///
|
||||||
|
/// Checks that the lease can be created, deleted and recreated with
|
||||||
|
/// different parameters. It also checks that the re-created lease is
|
||||||
|
/// correctly stored in the lease database.
|
||||||
|
TEST_F(MemfileLeaseMgrTest, testRecreateLease6) {
|
||||||
|
startBackend(V6);
|
||||||
|
testRecreateLease6();
|
||||||
|
}
|
||||||
|
|
||||||
// The following tests are not applicable for memfile. When adding
|
// The following tests are not applicable for memfile. When adding
|
||||||
// new tests to the list here, make sure to provide brief explanation
|
// new tests to the list here, make sure to provide brief explanation
|
||||||
|
@@ -478,4 +478,22 @@ TEST_F(MySqlLeaseMgrTest, updateLease6) {
|
|||||||
testUpdateLease6();
|
testUpdateLease6();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief DHCPv4 Lease recreation tests
|
||||||
|
///
|
||||||
|
/// Checks that the lease can be created, deleted and recreated with
|
||||||
|
/// different parameters. It also checks that the re-created lease is
|
||||||
|
/// correctly stored in the lease database.
|
||||||
|
TEST_F(MySqlLeaseMgrTest, testRecreateLease4) {
|
||||||
|
testRecreateLease4();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief DHCPv6 Lease recreation tests
|
||||||
|
///
|
||||||
|
/// Checks that the lease can be created, deleted and recreated with
|
||||||
|
/// different parameters. It also checks that the re-created lease is
|
||||||
|
/// correctly stored in the lease database.
|
||||||
|
TEST_F(MySqlLeaseMgrTest, testRecreateLease6) {
|
||||||
|
testRecreateLease6();
|
||||||
|
}
|
||||||
|
|
||||||
}; // Of anonymous namespace
|
}; // Of anonymous namespace
|
||||||
|
5
src/lib/dhcpsrv/tests/testdata/Makefile.am
vendored
5
src/lib/dhcpsrv/tests/testdata/Makefile.am
vendored
@@ -1,5 +0,0 @@
|
|||||||
SUBDIRS = .
|
|
||||||
|
|
||||||
# CSV files are created by unit tests which check the CSVLeaseFile6
|
|
||||||
# and CSVLeaseFile4 classes.
|
|
||||||
CLEANFILES = *.csv
|
|
@@ -13,6 +13,9 @@
|
|||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include <util/csv_file.h>
|
#include <util/csv_file.h>
|
||||||
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/algorithm/string/constants.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@@ -20,53 +23,21 @@ namespace isc {
|
|||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
CSVRow::CSVRow(const size_t cols, const char separator)
|
CSVRow::CSVRow(const size_t cols, const char separator)
|
||||||
: separator_(separator), values_(cols) {
|
: separator_(1, separator), values_(cols) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVRow::CSVRow(const std::string& text, const char separator)
|
CSVRow::CSVRow(const std::string& text, const char separator)
|
||||||
: separator_(separator) {
|
: separator_(1, separator) {
|
||||||
// Parsing is exception safe, so this will not throw.
|
// Parsing is exception safe, so this will not throw.
|
||||||
parse(text.c_str());
|
parse(text.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CSVRow::parse(const char* line) {
|
CSVRow::parse(const std::string& line) {
|
||||||
std::string s(line);
|
// Tokenize the string using a specified separator. Disable compression,
|
||||||
// The 'pos' value holds the current position in the parsed stream.
|
// so as the two consecutive separators mark an empty value.
|
||||||
// Normally, it points to the position of one of the the separator
|
boost::split(values_, line, boost::is_any_of(separator_),
|
||||||
// characters following the parsed value. For the first value, it
|
boost::algorithm::token_compress_off);
|
||||||
// has to be set to -1.
|
|
||||||
int pos = -1;
|
|
||||||
// Position of the first character of the currently parsed value.
|
|
||||||
size_t start_pos;
|
|
||||||
// Flag which indicates whether parsing should end because last value
|
|
||||||
// has been just parsed.
|
|
||||||
bool leave = false;
|
|
||||||
// Temporary container which holds parsed values. On successful
|
|
||||||
// parsing completion, the contents of this container are moved
|
|
||||||
// to the container holding values for the row.
|
|
||||||
std::vector<std::string> values;
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Set the position of the currently parsed value.
|
|
||||||
start_pos = pos + 1;
|
|
||||||
// Find the first separator, following the character at
|
|
||||||
// start_pos.
|
|
||||||
pos = s.find(separator_, start_pos);
|
|
||||||
// The last value is not followed by a separator, so if
|
|
||||||
// we reached the end of line, take reminder of the string
|
|
||||||
// and make it a value.
|
|
||||||
if (pos == std::string::npos) {
|
|
||||||
pos = s.length();
|
|
||||||
// Finish parsing as we already parsed the last value.
|
|
||||||
leave = true;
|
|
||||||
}
|
|
||||||
// Store the parsed value.
|
|
||||||
values.push_back(s.substr(start_pos, pos - start_pos));
|
|
||||||
} while (!leave);
|
|
||||||
|
|
||||||
// Assign new values.
|
|
||||||
std::swap(values, values_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
@@ -94,21 +65,6 @@ CSVRow::writeAt(const size_t at, const char* value) {
|
|||||||
values_[at] = value;
|
values_[at] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
CSVRow::writeAt(const size_t at, const std::string& value) {
|
|
||||||
writeAt(at, value.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
CSVRow::operator==(const CSVRow& other) const {
|
|
||||||
return (render() == other.render());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
CSVRow::operator!=(const CSVRow& other) const {
|
|
||||||
return (render() != other.render());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
|
std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
|
||||||
os << row.render();
|
os << row.render();
|
||||||
return (os);
|
return (os);
|
||||||
@@ -144,12 +100,23 @@ CSVFile::close() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
CSVFile::flush() const {
|
CSVFile::flush() const {
|
||||||
checkStreamStatus("flush");
|
checkStreamStatusAndReset("flush");
|
||||||
fs_->flush();
|
fs_->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CSVFile::addColumn(const std::string& col_name) {
|
CSVFile::addColumn(const std::string& col_name) {
|
||||||
|
// It is not allowed to add a new column when file is open.
|
||||||
|
if (fs_) {
|
||||||
|
isc_throw(CSVFileError, "attempt to add a column '" << col_name
|
||||||
|
<< "' while the file '" << getFilename()
|
||||||
|
<< "' is open");
|
||||||
|
}
|
||||||
|
addColumnInternal(col_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CSVFile::addColumnInternal(const std::string& col_name) {
|
||||||
if (getColumnIndex(col_name) >= 0) {
|
if (getColumnIndex(col_name) >= 0) {
|
||||||
isc_throw(CSVFileError, "attempt to add duplicate column '"
|
isc_throw(CSVFileError, "attempt to add duplicate column '"
|
||||||
<< col_name << "'");
|
<< col_name << "'");
|
||||||
@@ -159,10 +126,7 @@ CSVFile::addColumn(const std::string& col_name) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
CSVFile::append(const CSVRow& row) const {
|
CSVFile::append(const CSVRow& row) const {
|
||||||
checkStreamStatus("append");
|
checkStreamStatusAndReset("append");
|
||||||
|
|
||||||
// If a stream is in invalid state, reset the state.
|
|
||||||
fs_->clear();
|
|
||||||
|
|
||||||
if (row.getValuesCount() != getColumnCount()) {
|
if (row.getValuesCount() != getColumnCount()) {
|
||||||
isc_throw(CSVFileError, "number of values in the CSV row '"
|
isc_throw(CSVFileError, "number of values in the CSV row '"
|
||||||
@@ -170,6 +134,14 @@ CSVFile::append(const CSVRow& row) const {
|
|||||||
" columns in the CSV file '" << getColumnCount() << "'");
|
" columns in the CSV file '" << getColumnCount() << "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @todo Apparently, seekp and seekg are interchangable. A call to seekp
|
||||||
|
/// results in moving the input pointer too. This is ok for now. It means
|
||||||
|
/// that when the append() is called, the read pointer is moved to the EOF.
|
||||||
|
/// For the current use cases we only read a file and then append a new
|
||||||
|
/// content. If we come up with the scenarios when read and write is
|
||||||
|
/// needed at the same time, we may revisit this: perhaps remember the
|
||||||
|
/// old pointer. Also, for safety, we call both functions so as we are
|
||||||
|
/// sure that both pointers are moved.
|
||||||
fs_->seekp(0, std::ios_base::end);
|
fs_->seekp(0, std::ios_base::end);
|
||||||
fs_->seekg(0, std::ios_base::end);
|
fs_->seekg(0, std::ios_base::end);
|
||||||
fs_->clear();
|
fs_->clear();
|
||||||
@@ -184,15 +156,18 @@ CSVFile::append(const CSVRow& row) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CSVFile::checkStreamStatus(const std::string& operation) const {
|
CSVFile::checkStreamStatusAndReset(const std::string& operation) const {
|
||||||
if (!fs_) {
|
if (!fs_) {
|
||||||
isc_throw(CSVFileError, "NULL stream pointer when performing '"
|
isc_throw(CSVFileError, "NULL stream pointer when performing '"
|
||||||
<< operation << "' on file '" << filename_ << "'");
|
<< operation << "' on file '" << filename_ << "'");
|
||||||
|
|
||||||
} else if (!fs_->is_open()) {
|
} else if (!fs_->is_open()) {
|
||||||
|
fs_->clear();
|
||||||
isc_throw(CSVFileError, "closed stream when performing '"
|
isc_throw(CSVFileError, "closed stream when performing '"
|
||||||
<< operation << "' on file '" << filename_ << "'");
|
<< operation << "' on file '" << filename_ << "'");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fs_->clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +206,7 @@ CSVFile::getColumnIndex(const std::string& col_name) const {
|
|||||||
|
|
||||||
std::string
|
std::string
|
||||||
CSVFile::getColumnName(const size_t col_index) const {
|
CSVFile::getColumnName(const size_t col_index) const {
|
||||||
if (col_index > cols_.size()) {
|
if (col_index >= cols_.size()) {
|
||||||
isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
|
isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
|
||||||
" CSV file '" << filename_ << "' is out of range; the CSV"
|
" CSV file '" << filename_ << "' is out of range; the CSV"
|
||||||
" file has only " << cols_.size() << " columns ");
|
" file has only " << cols_.size() << " columns ");
|
||||||
@@ -248,16 +223,13 @@ CSVFile::next(CSVRow& row, const bool skip_validation) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Check that stream is "ready" for any IO operations.
|
// Check that stream is "ready" for any IO operations.
|
||||||
checkStreamStatus("get next row");
|
checkStreamStatusAndReset("get next row");
|
||||||
|
|
||||||
} catch (isc::Exception& ex) {
|
} catch (isc::Exception& ex) {
|
||||||
setReadMsg(ex.what());
|
setReadMsg(ex.what());
|
||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a stream is in invalid state, reset the state.
|
|
||||||
fs_->clear();
|
|
||||||
|
|
||||||
// Get exactly one line of the file.
|
// Get exactly one line of the file.
|
||||||
std::string line;
|
std::string line;
|
||||||
std::getline(*fs_, line);
|
std::getline(*fs_, line);
|
||||||
@@ -275,7 +247,7 @@ CSVFile::next(CSVRow& row, const bool skip_validation) {
|
|||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
// If we read anything, parse it.
|
// If we read anything, parse it.
|
||||||
row.parse(line.c_str());
|
row.parse(line);
|
||||||
|
|
||||||
// And check if it is correct.
|
// And check if it is correct.
|
||||||
return (skip_validation ? true : validate(row));
|
return (skip_validation ? true : validate(row));
|
||||||
@@ -290,44 +262,47 @@ CSVFile::open() {
|
|||||||
} else {
|
} else {
|
||||||
// Try to open existing file, holding some data.
|
// Try to open existing file, holding some data.
|
||||||
fs_.reset(new std::fstream(filename_.c_str()));
|
fs_.reset(new std::fstream(filename_.c_str()));
|
||||||
// The file may fail to open. For example, because of insufficient
|
|
||||||
// persmissions. Although the file is not open we should call close
|
|
||||||
// to reset our internal pointer.
|
|
||||||
if (!fs_->is_open()) {
|
|
||||||
close();
|
|
||||||
isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
|
|
||||||
}
|
|
||||||
// Make sure we are on the beginning of the file, so as we can parse
|
|
||||||
// the header.
|
|
||||||
fs_->seekg(0);
|
|
||||||
if (!fs_->good()) {
|
|
||||||
close();
|
|
||||||
isc_throw(CSVFileError, "unable to set read pointer in the file '"
|
|
||||||
<< filename_ << "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the header.
|
// Catch exceptions so as we can close the file if error occurs.
|
||||||
CSVRow header;
|
try {
|
||||||
if (!next(header, true)) {
|
// The file may fail to open. For example, because of insufficient
|
||||||
close();
|
// persmissions. Although the file is not open we should call close
|
||||||
isc_throw(CSVFileError, "failed to read and parse header of the"
|
// to reset our internal pointer.
|
||||||
" CSV file '" << filename_ << "': "
|
if (!fs_->is_open()) {
|
||||||
<< getReadMsg());
|
isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
|
||||||
}
|
|
||||||
|
|
||||||
// Check the header against the columns specified for the CSV file.
|
|
||||||
if (!validateHeader(header)) {
|
|
||||||
close();
|
|
||||||
isc_throw(CSVFileError, "invalid header '" << header
|
|
||||||
<< "' in CSV file '" << filename_ << "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is good, so if we haven't added any columns yet,
|
|
||||||
// add them.
|
|
||||||
if (getColumnCount() == 0) {
|
|
||||||
for (size_t i = 0; i < header.getValuesCount(); ++i) {
|
|
||||||
addColumn(header.readAt(i));
|
|
||||||
}
|
}
|
||||||
|
// Make sure we are on the beginning of the file, so as we can parse
|
||||||
|
// the header.
|
||||||
|
fs_->seekg(0);
|
||||||
|
if (!fs_->good()) {
|
||||||
|
isc_throw(CSVFileError, "unable to set read pointer in the file '"
|
||||||
|
<< filename_ << "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the header.
|
||||||
|
CSVRow header;
|
||||||
|
if (!next(header, true)) {
|
||||||
|
isc_throw(CSVFileError, "failed to read and parse header of the"
|
||||||
|
" CSV file '" << filename_ << "': "
|
||||||
|
<< getReadMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the header against the columns specified for the CSV file.
|
||||||
|
if (!validateHeader(header)) {
|
||||||
|
isc_throw(CSVFileError, "invalid header '" << header
|
||||||
|
<< "' in CSV file '" << filename_ << "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything is good, so if we haven't added any columns yet,
|
||||||
|
// add them.
|
||||||
|
if (getColumnCount() == 0) {
|
||||||
|
for (size_t i = 0; i < header.getValuesCount(); ++i) {
|
||||||
|
addColumnInternal(header.readAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
close();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -103,7 +103,7 @@ public:
|
|||||||
/// This function is exception-free.
|
/// This function is exception-free.
|
||||||
///
|
///
|
||||||
/// @param line String holding a row of comma separated values.
|
/// @param line String holding a row of comma separated values.
|
||||||
void parse(const char* line);
|
void parse(const std::string& line);
|
||||||
|
|
||||||
/// @brief Retrieves a value from the internal container.
|
/// @brief Retrieves a value from the internal container.
|
||||||
///
|
///
|
||||||
@@ -174,7 +174,9 @@ public:
|
|||||||
/// @param value Value to be written given as string.
|
/// @param value Value to be written given as string.
|
||||||
///
|
///
|
||||||
/// @throw CSVFileError if index is out of range.
|
/// @throw CSVFileError if index is out of range.
|
||||||
void writeAt(const size_t at, const std::string& value);
|
void writeAt(const size_t at, const std::string& value) {
|
||||||
|
writeAt(at, value.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Replaces the value at specified index.
|
/// @brief Replaces the value at specified index.
|
||||||
///
|
///
|
||||||
@@ -204,7 +206,9 @@ public:
|
|||||||
/// includes the order of fields, separator etc.
|
/// includes the order of fields, separator etc.
|
||||||
///
|
///
|
||||||
/// @param other Object to compare to.
|
/// @param other Object to compare to.
|
||||||
bool operator==(const CSVRow& other) const;
|
bool operator==(const CSVRow& other) const {
|
||||||
|
return (render() == other.render());
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Unequality operator.
|
/// @brief Unequality operator.
|
||||||
///
|
///
|
||||||
@@ -212,7 +216,9 @@ public:
|
|||||||
/// This includes the order of fields, separator etc.
|
/// This includes the order of fields, separator etc.
|
||||||
///
|
///
|
||||||
/// @param other Object to compare to.
|
/// @param other Object to compare to.
|
||||||
bool operator!=(const CSVRow& other) const;
|
bool operator!=(const CSVRow& other) const {
|
||||||
|
return (render() != other.render());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@@ -225,7 +231,12 @@ private:
|
|||||||
void checkIndex(const size_t at) const;
|
void checkIndex(const size_t at) const;
|
||||||
|
|
||||||
/// @brief Separator character specifed in the constructor.
|
/// @brief Separator character specifed in the constructor.
|
||||||
char separator_;
|
///
|
||||||
|
/// @note Separator is held as a string object (one character long),
|
||||||
|
/// because the boost::is_any_of algorithm requires a string, not a
|
||||||
|
/// char value. If we held the separator as a char, we would need to
|
||||||
|
/// convert it to string on every call to @c CSVRow::parse.
|
||||||
|
std::string separator_;
|
||||||
|
|
||||||
/// @brief Internal container holding values that belong to the row.
|
/// @brief Internal container holding values that belong to the row.
|
||||||
std::vector<std::string> values_;
|
std::vector<std::string> values_;
|
||||||
@@ -388,6 +399,19 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
/// @brief Adds a column regardless if the file is open or not.
|
||||||
|
///
|
||||||
|
/// This function adds as new column to the collection. It is meant to be
|
||||||
|
/// called internally by the methods of the base class and derived classes.
|
||||||
|
/// It must not be used in the public scope. The @c CSVFile::addColumn
|
||||||
|
/// must be used in the public scope instead, because it prevents addition
|
||||||
|
/// of the new column when the file is open.
|
||||||
|
///
|
||||||
|
/// @param col_name Name of the column.
|
||||||
|
///
|
||||||
|
/// @throw CSVFileError if a column with the specified name exists.
|
||||||
|
void addColumnInternal(const std::string& col_name);
|
||||||
|
|
||||||
/// @brief Validate the row read from a file.
|
/// @brief Validate the row read from a file.
|
||||||
///
|
///
|
||||||
/// This function implements a basic validation for the row read from the
|
/// This function implements a basic validation for the row read from the
|
||||||
@@ -425,10 +449,11 @@ private:
|
|||||||
/// Checks if the file stream is open so as IO operations can be performed
|
/// Checks if the file stream is open so as IO operations can be performed
|
||||||
/// on it. This is internally called by the public class members to prevent
|
/// on it. This is internally called by the public class members to prevent
|
||||||
/// them from performing IO operations on invalid stream and using NULL
|
/// them from performing IO operations on invalid stream and using NULL
|
||||||
/// pointer to a stream.
|
/// pointer to a stream. The @c clear() method is called on the stream
|
||||||
|
/// after the status has been checked.
|
||||||
///
|
///
|
||||||
/// @throw CSVFileError if stream is closed or pointer to it is NULL.
|
/// @throw CSVFileError if stream is closed or pointer to it is NULL.
|
||||||
void checkStreamStatus(const std::string& operation) const;
|
void checkStreamStatusAndReset(const std::string& operation) const;
|
||||||
|
|
||||||
/// @brief Returns size of the CSV file.
|
/// @brief Returns size of the CSV file.
|
||||||
std::ifstream::pos_type size() const;
|
std::ifstream::pos_type size() const;
|
||||||
|
@@ -191,6 +191,32 @@ CSVFileTest::writeFile(const std::string& contents) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test checks that the function which is used to add columns of the
|
||||||
|
// CSV file works as expected.
|
||||||
|
TEST_F(CSVFileTest, addColumn) {
|
||||||
|
boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
|
||||||
|
// Add two columns.
|
||||||
|
ASSERT_NO_THROW(csv->addColumn("animal"));
|
||||||
|
ASSERT_NO_THROW(csv->addColumn("color"));
|
||||||
|
// Make sure we can't add duplicates.
|
||||||
|
EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
|
||||||
|
EXPECT_THROW(csv->addColumn("color"), CSVFileError);
|
||||||
|
// But we should still be able to add unique columns.
|
||||||
|
EXPECT_NO_THROW(csv->addColumn("age"));
|
||||||
|
EXPECT_NO_THROW(csv->addColumn("comments"));
|
||||||
|
// Assert that the file is opened, because the rest of the test relies
|
||||||
|
// on this.
|
||||||
|
ASSERT_NO_THROW(csv->recreate());
|
||||||
|
ASSERT_TRUE(exists());
|
||||||
|
|
||||||
|
// Make sure we can't add columns (even unique) when the file is open.
|
||||||
|
ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
|
||||||
|
// Close the file.
|
||||||
|
ASSERT_NO_THROW(csv->close());
|
||||||
|
// And check that now it is possible to add the column.
|
||||||
|
EXPECT_NO_THROW(csv->addColumn("zoo"));
|
||||||
|
}
|
||||||
|
|
||||||
// This test checks that the appropriate file name is initialized.
|
// This test checks that the appropriate file name is initialized.
|
||||||
TEST_F(CSVFileTest, getFilename) {
|
TEST_F(CSVFileTest, getFilename) {
|
||||||
CSVFile csv(testfile_);
|
CSVFile csv(testfile_);
|
||||||
|
Reference in New Issue
Block a user