Commit fbae37a0 authored by Marcin Siodelski's avatar Marcin Siodelski

[3360] Implemented CSV parser for DHCPv6 leases.

parent 3150e9c0
......@@ -1535,7 +1535,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/lib/dhcpsrv/Makefile
src/lib/dhcpsrv/tests/Makefile
src/lib/dhcpsrv/tests/test_libraries.h
srv/lib/dhcpsrv/tests/testdata/Makefile
src/lib/dhcpsrv/tests/testdata/Makefile
src/lib/dhcp/tests/Makefile
src/lib/dns/benchmarks/Makefile
src/lib/dns/gen-rdatacode.py
......
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2014 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
......@@ -14,8 +14,11 @@
#include <dhcp/duid.h>
#include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <util/io_utilities.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iomanip>
#include <sstream>
#include <vector>
......@@ -62,6 +65,37 @@ DUID::DUIDType DUID::getType() const {
}
}
DUID
DUID::fromText(const std::string& text) {
/// @todo optimize stream operations here.
std::vector<std::string> split_text;
boost::split(split_text, text, boost::is_any_of(":"),
boost::algorithm::token_compress_on);
std::ostringstream s;
for (size_t i = 0; i < split_text.size(); ++i) {
if (split_text[i].size() == 1) {
s << "0";
} else if (split_text[i].size() > 2) {
isc_throw(isc::BadValue, "invalid DUID '" << text << "'");
}
s << split_text[i];
}
if (s.str().empty()) {
isc_throw(isc::BadValue, "empty DUID is not allowed");
}
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create DUID from text '"
<< text << "': " << ex.what());
}
return DUID(&binary[0], binary.size());
}
std::string DUID::toText() const {
std::stringstream tmp;
tmp << std::hex;
......
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2014 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
......@@ -69,6 +69,23 @@ class DUID {
/// @brief Returns the DUID type
DUIDType getType() const;
/// @brief Create DUID from the textual format.
///
/// This static function parses a DUID specified in the textual format.
/// The format being parsed should match the DUID representation returned
/// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
/// representing bytes of DUID must be separated by colons. Usually the
/// single byte is represented by two hexadecimal digits. However, this
/// function allows one digit per byte. In this case, a zero is prepended
/// before the conversion. For example, a DUID 0:1:2::4:5 equals to
/// 00:01:02:00:04:05.
///
/// @param text DUID in the hexadecimal format with digits representing
/// individual bytes separated by colons.
///
/// @throw isc::BadValue if parsing the DUID failed.
static DUID fromText(const std::string& text);
/// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
std::string toText() const;
......
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2014 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
......@@ -126,6 +126,37 @@ TEST(DuidTest, getType) {
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}
// This test checks that the DUID instance can be created from the textual
// format and that error is reported if the textual format is invalid.
TEST(DuidTest, fromText) {
scoped_ptr<DUID> duid;
// DUID with only decimal digits.
ASSERT_NO_THROW(
duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06")))
);
EXPECT_EQ("00:01:02:03:04:05:06", duid->toText());
// DUID with some hexadecimal digits (upper case and lower case).
ASSERT_NO_THROW(
duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab")))
);
EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText());
// DUID with one digit for a particular byte.
ASSERT_NO_THROW(
duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
);
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
// Repeated colon sign in the DUID should be ignored.
ASSERT_NO_THROW(
duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab")))
);
EXPECT_EQ("00:bb:0d:ee:ef:ab", duid->toText());
// DUID with excessive number of digits for one of the bytes.
EXPECT_THROW(
duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
isc::BadValue
);
}
// Test checks if the toText() returns valid texual representation
TEST(DuidTest, toText) {
uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
......
......@@ -13,7 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcpsrv/csv_lease_file6.h>
#include <algorithm>
using namespace isc::asiolink;
using namespace isc::util;
namespace isc {
......@@ -59,6 +61,112 @@ CSVLeaseFile6::append(const Lease6& lease) const {
CSVFile::append(row);
}
bool
CSVLeaseFile6::next(Lease6Ptr& lease) {
lease.reset();
CSVRow row;
CSVFile::next(row);
if (row == CSVFile::EMPTY_ROW()) {
return (true);
}
try {
lease.reset(new Lease6(readType(row), readAddress(row), readDUID(row),
readIAID(row), readPreferred(row),
readValid(row), 0, 0, readSubnetID(row),
readPrefixLen(row)));
lease->cltt_ = readCltt(row);
lease->fqdn_fwd_ = readFqdnFwd(row);
lease->fqdn_rev_ = readFqdnRev(row);
lease->hostname_ = readhostname(row);
} catch (std::exception& ex) {
lease.reset();
setReadMsg(ex.what());
return (false);
}
return (true);
}
Lease::Type
CSVLeaseFile6::readType(const CSVRow& row) {
return (static_cast<Lease::Type>
(row.readAndConvertAt<int>(getColumnIndex("lease_type"))));
}
IOAddress
CSVLeaseFile6::readAddress(const CSVRow& row) {
IOAddress address(row.readAt(getColumnIndex("address")));
return (address);
}
DuidPtr
CSVLeaseFile6::readDUID(const util::CSVRow& row) {
DuidPtr duid(new DUID(DUID::fromText(row.readAt(getColumnIndex("duid")))));
return (duid);
}
uint32_t
CSVLeaseFile6::readIAID(const CSVRow& row) {
uint32_t iaid = row.readAndConvertAt<uint32_t>(getColumnIndex("iaid"));
return (iaid);
}
uint32_t
CSVLeaseFile6::readPreferred(const CSVRow& row) {
uint32_t pref =
row.readAndConvertAt<uint32_t>(getColumnIndex("pref_lifetime"));
return (pref);
}
uint32_t
CSVLeaseFile6::readValid(const CSVRow& row) {
uint32_t valid =
row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
return (valid);
}
uint32_t
CSVLeaseFile6::readCltt(const CSVRow& row) {
uint32_t cltt = row.readAndConvertAt<uint32_t>(getColumnIndex("expire"))
- readValid();
return (cltt);
}
SubnetID
CSVLeaseFile6::readSubnetID(const CSVRow& row) {
SubnetID subnet_id =
row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
return (subnet_id);
}
uint8_t
CSVLeaseFile6::readPrefixLen(const CSVRow& row) {
int prefixlen = row.readAndConvertAt<int>(getColumnIndex("prefix_len"));
return (static_cast<uint8_t>(prefixlen));
}
bool
CSVLeaseFile6::readFqdnFwd(const CSVRow& row) {
bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
return (fqdn_fwd);
}
bool
CSVLeaseFile6::readFqdnRev(const CSVRow& row) {
bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
return (fqdn_rev);
}
std::string
CSVLeaseFile6::readHostname(const CSVRow& row) {
std::string hostname = row.readAt(getColumnIndex("hostname"));
return (hostname);
}
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -15,23 +15,133 @@
#ifndef CSV_LEASE_FILE6_H
#define CSV_LEASE_FILE6_H
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/subnet.h>
#include <util/csv_file.h>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <string>
namespace isc {
namespace dhcp {
/// @brief Provides methods to access CSV file with DHCPv6 leases.
class CSVLeaseFile6 : public isc::util::CSVFile {
public:
/// @brief Constructor.
///
/// Initializes columns of the lease file.
///
/// @param filename Name of the lease file.
CSVLeaseFile6(const std::string& filename);
/// @brief Appends the lease record to the CSV file.
///
/// @param lease Structure representing a DHCPv6 lease.
void append(const Lease6& lease) const;
/// @brief Reads next lease from the CSV file.
///
/// If this function hits an error during lease read, it sets the error
/// message using @c CSVFile::setReadMsg and returns false. The error
/// string may be read using @c CSVFile::getReadMsg.
///
/// @param [out] lease Pointer to the lease read from CSV file or
/// NULL pointer if lease hasn't been read.
///
/// @return Boolean value indicating that the new lease has been
/// read from the CSV file (if true), or that the error has occurred
/// (false).
bool next(Lease6Ptr& lease);
private:
/// @brief Initializes columns of the CSV file holding leases.
///
/// This function initializes the following columns:
/// - address
/// - duid
/// - valid_lifetime
/// - expire
/// - subnet_id
/// - pref_lifetime
/// - lease_type
/// - iaid
/// - prefix_len
/// - fqdn_fwd
/// - fqdn_rev
/// - hostname
void initColumns();
///
/// @name Methods which read specific lease fields from the CSV row.
///
//@{
///
/// @brief Reads lease type from the CSV file row.
///
/// @param row CSV file holding lease values.
Lease::Type readType(const util::CSVRow& row);
/// @brief Reads lease address from the CSV file row.
///
/// @param row CSV file holding lease values.
asiolink::IOAddress readAddress(const util::CSVRow& row);
/// @brief Reads DUID from the CSV file row.
///
/// @param row CSV file holding lease values.
DuidPtr readDUID(const util::CSVRow& row);
/// @brief Reads IAID from the CSV file row.
///
/// @param row CSV file holding lease values.
uint32_t readIAID(const util::CSVRow& row);
/// @brief Reads preferred lifetime from the CSV file row.
///
/// @param row CSV file holding lease values.
uint32_t readPreferred(const util::CSVRow& row);
/// @brief Reads valid lifetime from the CSV file row.
///
/// @param row CSV file holding lease values.
uint32_t readValid(const util::CSVRow& row);
/// @brief Reads cltt value from the CSV file row.
///
/// @param row CSV file holding lease values.
uint32_t readCltt(const util::CSVRow& row);
/// @brief Reads subnet id from the CSV file row.
///
/// @param row CSV file holding lease values.
SubnetID readSubnetID(const util::CSVRow& row);
/// @brief Reads prefix length from the CSV file row.
///
/// @param row CSV file holding lease values.
uint8_t readPrefixLen(const util::CSVRow& row);
/// @brief Reads the FQDN forward flag from the CSV file row.
///
/// @param row CSV file holding lease values.
bool readFqdnFwd(const util::CSVRow& row);
/// @brief Reads the FQDN reverse flag from the CSV file row.
///
/// @param row CSV file holding lease values.
bool readFqdnRev(const util::CSVRow& row);
/// @brief Reads hostname from the CSV file row.
///
/// @param row CSV file holding lease values.
std::string readHostname(const util::CSVRow& row);
//@}
};
} // namespace isc::dhcp
......
......@@ -2,8 +2,8 @@ SUBDIRS = . testdata
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests/testdata\"
AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests/testdata\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
......@@ -15,9 +15,6 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
CLEANFILES = *.gcno *.gcda
# CSV files are created by unit tests which check the CSVLeaseFile6
# and CSVLeaseFile4 classes.
CLEANFILES += *.csv
TESTS_ENVIRONMENT = \
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
......
......@@ -14,10 +14,12 @@
#include <config.h>
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcpsrv/csv_lease_file6.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/tests/lease_file_io.h>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
#include <sstream>
......@@ -64,6 +66,72 @@ CSVLeaseFile6Test::absolutePath(const std::string& filename) {
return (s.str());
}
TEST_F(CSVLeaseFile6Test, parse) {
boost::scoped_ptr<CSVLeaseFile6>
lf(new CSVLeaseFile6(absolutePath("leases6_0.csv")));
ASSERT_NO_THROW(lf->open());
Lease6Ptr lease;
bool lease_read = false;
ASSERT_NO_THROW(lease_read = lf->next(lease));
ASSERT_TRUE(lease);
EXPECT_TRUE(lease_read);
EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
ASSERT_TRUE(lease->duid_);
EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
EXPECT_EQ(200, lease->valid_lft_);
EXPECT_EQ(0, lease->cltt_);
EXPECT_EQ(8, lease->subnet_id_);
EXPECT_EQ(100, lease->preferred_lft_);
EXPECT_EQ(Lease::TYPE_NA, lease->type_);
EXPECT_EQ(7, lease->iaid_);
EXPECT_EQ(0, lease->prefixlen_);
EXPECT_TRUE(lease->fqdn_fwd_);
EXPECT_TRUE(lease->fqdn_rev_);
EXPECT_EQ("host.example.com", lease->hostname_);
EXPECT_FALSE(lease_read = lf->next(lease));
ASSERT_NO_THROW(lease_read = lf->next(lease));
ASSERT_TRUE(lease);
EXPECT_TRUE(lease_read);
EXPECT_EQ("2001:db8:2::10", lease->addr_.toText());
ASSERT_TRUE(lease->duid_);
EXPECT_EQ("01:01:01:01:0a:01:02:03:04:05", lease->duid_->toText());
EXPECT_EQ(300, lease->valid_lft_);
EXPECT_EQ(0, lease->cltt_);
EXPECT_EQ(6, lease->subnet_id_);
EXPECT_EQ(150, lease->preferred_lft_);
EXPECT_EQ(Lease::TYPE_NA, lease->type_);
EXPECT_EQ(8, lease->iaid_);
EXPECT_EQ(0, lease->prefixlen_);
EXPECT_FALSE(lease->fqdn_fwd_);
EXPECT_FALSE(lease->fqdn_rev_);
EXPECT_TRUE(lease->hostname_.empty());
EXPECT_FALSE(lease_read = lf->next(lease));
ASSERT_NO_THROW(lease_read = lf->next(lease));
ASSERT_TRUE(lease);
EXPECT_TRUE(lease_read);
EXPECT_EQ("3000:1::", lease->addr_.toText());
ASSERT_TRUE(lease->duid_);
EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
EXPECT_EQ(0, lease->valid_lft_);
EXPECT_EQ(200, lease->cltt_);
EXPECT_EQ(8, lease->subnet_id_);
EXPECT_EQ(0, lease->preferred_lft_);
EXPECT_EQ(Lease::TYPE_PD, lease->type_);
EXPECT_EQ(16, lease->iaid_);
EXPECT_EQ(64, lease->prefixlen_);
EXPECT_FALSE(lease->fqdn_fwd_);
EXPECT_FALSE(lease->fqdn_rev_);
EXPECT_TRUE(lease->hostname_.empty());
ASSERT_NO_THROW(lease_read = lf->next(lease));
EXPECT_TRUE(lease_read);
EXPECT_FALSE(lease);
}
TEST_F(CSVLeaseFile6Test, recreate) {
boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
......
SUBDIRS = .
# CSV files are created by unit tests which check the CSVLeaseFile6
# and CSVLeaseFile4 classes.
CLEANFILES = *.csv
EXTRA_DIST = leases6_0.csv
address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname
2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,8,100,0,7,0,1,1,host.example.com
2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com
2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,300,6,150,0,8,0,0,0,
2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,8,100,0,7,10,1,1,host.example.com
3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,0,200,8,0,2,16,64,0,0,
......@@ -118,6 +118,32 @@ public:
/// @c CSVRow::getValuesCount.
std::string readAt(const size_t at) const;
/// @brief Retrieves a value from the internal container.
///
/// This method is reads a value from the internal container and converts
/// this value to the type specified as a template parameter. Internally
/// it uses @c boost::lexical_cast.
///
/// @param at Index of the value in the container. The values are indexed
/// from 0, where 0 corresponds to the left-most value in the CSV file row.
/// @tparam T type of the value to convert to.
///
/// @return Converted value.
///
/// @throw CSVFileError if the index is out of range or if the
/// @c boost::bad_lexical_cast is thrown by the @c boost::lexical_cast.
template<typename T>
T readAndConvertAt(const size_t at) const {
T cast_value;
try {
cast_value = boost::lexical_cast<T>(readAt(at).c_str());
} catch (const boost::bad_lexical_cast& ex) {
isc_throw(CSVFileError, ex.what());
}
return (cast_value);
}
/// @brief Creates a text representation of the CSV file row.
///
/// This function iterates over all values currently held in the internal
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment