Browse Source

[3360] Implemented CSV parser for DHCPv6 leases.

Marcin Siodelski 11 years ago
parent
commit
fbae37a0d6

+ 1 - 1
configure.ac

@@ -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

+ 36 - 2
src/lib/dhcp/duid.cc

@@ -1,4 +1,4 @@
-// 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;

+ 18 - 1
src/lib/dhcp/duid.h

@@ -1,4 +1,4 @@
-// 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;
 

+ 32 - 1
src/lib/dhcp/tests/duid_unittest.cc

@@ -1,4 +1,4 @@
-// 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};

+ 108 - 0
src/lib/dhcpsrv/csv_lease_file6.cc

@@ -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

+ 110 - 0
src/lib/dhcpsrv/csv_lease_file6.h

@@ -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 - 5
src/lib/dhcpsrv/tests/Makefile.am

@@ -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)

+ 68 - 0
src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc

@@ -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_));

+ 4 - 0
src/lib/dhcpsrv/tests/testdata/Makefile.am

@@ -1,4 +1,8 @@
 SUBDIRS = .
 
+# CSV files are created by unit tests which check the CSVLeaseFile6
+# and CSVLeaseFile4 classes.
+CLEANFILES = *.csv
+
 EXTRA_DIST = leases6_0.csv
 

+ 6 - 0
src/lib/dhcpsrv/tests/testdata/leases6_0.csv

@@ -0,0 +1,6 @@
+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,

+ 26 - 0
src/lib/util/csv_file.h

@@ -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