Browse Source

[4321] Added basic test for multiple v6 reservations.

Marcin Siodelski 9 years ago
parent
commit
2e3cedb832

+ 86 - 4
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -13,6 +13,7 @@
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/lease.h>
+#include <dhcpsrv/pool.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <util/buffer.h>
 #include <boost/foreach.hpp>
@@ -20,6 +21,7 @@
 #include <cstdlib>
 #include <time.h>
 
+using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 
@@ -63,6 +65,25 @@ struct getLeasesByPropertyFun {
     }
 };
 
+/// @brief Returns leases which belong to specified pool.
+///
+/// @param config DHCP client configuration structure holding leases.
+/// @param pool Pool to which returned leases belong.
+/// @param [out] leases A vector in which the function will store leases
+/// found.
+void getLeasesByPool(const Dhcp6Client::Configuration& config,
+                     const Pool6& pool, std::vector<Lease6>& leases) {
+    for (std::vector<Lease6>::const_iterator lease =
+             config.leases_.begin(); lease != config.leases_.end();
+         ++lease) {
+        // Check if prefix in range.
+        if (pool.inRange(lease->addr_)) {
+            // Found the matching lease.
+            leases.push_back(*lease);
+        }
+    }
+}
+
 }; // end of anonymous namespace
 
 namespace isc {
@@ -392,7 +413,7 @@ Dhcp6Client::doSARR() {
 }
 
 void
-Dhcp6Client::doSolicit() {
+Dhcp6Client::doSolicit(const bool always_apply_config) {
     context_.query_ = createMsg(DHCPV6_SOLICIT);
     if (forced_server_id_) {
         context_.query_->addOption(forced_server_id_);
@@ -412,9 +433,10 @@ Dhcp6Client::doSolicit() {
     context_.response_ = receiveOneMsg();
 
     // If using Rapid Commit and the server has responded with Reply,
-    // let's apply received configuration.
-    if (use_rapid_commit_ && context_.response_ &&
-        context_.response_->getType() == DHCPV6_REPLY) {
+    // let's apply received configuration. We also apply the configuration
+    // for the Advertise if instructed to do so.
+    if (context_.response_ && (always_apply_config || (use_rapid_commit_ &&
+        context_.response_->getType() == DHCPV6_REPLY))) {
         config_.clear();
         applyRcvdConfiguration(context_.response_);
     }
@@ -648,6 +670,66 @@ Dhcp6Client::getLeasesWithZeroLifetime() const {
     return (leases);
 }
 
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddress(const IOAddress& address) const {
+    std::vector<Lease6> leases;
+    getLeasesByProperty<Lease, IOAddress, &Lease::addr_>(address, true, leases);
+    return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddressRange(const IOAddress& first,
+                                     const IOAddress& second) const {
+    std::vector<Lease6> leases;
+    getLeasesByPool(config_, Pool6(Lease::TYPE_NA, first, second), leases);
+    return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+                                   const uint8_t prefix_len,
+                                   const uint8_t delegated_len) const {
+    std::vector<Lease6> leases;
+    getLeasesByPool(config_, Pool6(Lease::TYPE_PD, prefix, prefix_len,
+                                   delegated_len), leases);
+    return (leases);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const {
+    std::vector<Lease6> leases = getLeasesByAddress(address);
+    return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first,
+                                     const asiolink::IOAddress& last) const {
+    std::vector<Lease6> leases = getLeasesByAddressRange(first, last);
+    return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+                               const uint8_t prefix_len) const {
+    std::vector<Lease6> leases = getLeasesByAddress(prefix);
+    BOOST_FOREACH(const Lease6& lease, leases) {
+        if (lease.prefixlen_ == prefix_len) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+                                   const uint8_t prefix_len,
+                                   const uint8_t delegated_len) const {
+    std::vector<Lease6> leases = getLeasesByPrefixPool(prefix, prefix_len,
+                                                       delegated_len);
+    return (!leases.empty());
+}
+
+
 uint16_t
 Dhcp6Client::getStatusCode(const uint32_t iaid) const {
     std::map<uint32_t, uint16_t>::const_iterator status_code =

+ 71 - 1
src/bin/dhcp6/tests/dhcp6_client.h

@@ -13,6 +13,7 @@
 #include <dhcp/option6_client_fqdn.h>
 #include <dhcpsrv/lease.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
+#include <util/staged_value.h>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <list>
@@ -226,12 +227,15 @@ public:
     /// i.e. sends a Solicit to the server and receives Advertise. It doesn't
     /// set the lease configuration in the @c config_.
     ///
+    /// @param always_apply_config Apply received configuration even if the
+    /// Advertise message is received. Default value is false.
+    ///
     /// @throw This function doesn't throw exceptions on its own, but it calls
     /// functions that are not exception safe, so it may throw exceptions if
     /// error occurs.
     ///
     /// @todo Perform sanity checks on returned messages.
-    void doSolicit();
+    void doSolicit(const bool always_apply_config = false);
 
     /// @brief Sends a Renew to the server and receives the Reply.
     ///
@@ -358,6 +362,72 @@ public:
     /// @brief Returns leases with zero lifetimes.
     std::vector<Lease6> getLeasesWithZeroLifetime() const;
 
+    /// @brief Returns leases by lease address/prefix.
+    ///
+    /// @param address Leased address.
+    ///
+    /// @return Vector containing leases for the specified address.
+    std::vector<Lease6> getLeasesByAddress(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns leases belonging to specified address range.
+    ///
+    /// @param first Lower bound of the address range.
+    /// @param second Upper bound of the address range.
+    ///
+    /// @return Vector containing leases belonging to specified address range.
+    std::vector<Lease6> getLeasesByAddressRange(const asiolink::IOAddress& first,
+                                                const asiolink::IOAddress& second) const;
+
+    /// @brief Returns leases belonging to prefix pool.
+    ///
+    /// @param prefix Prefix of the pool.
+    /// @param prefix_len Prefix length.
+    /// @param delegated_len Delegated prefix length.
+    ///
+    /// @return Vector containing leases belonging to specified prefix pool.
+    std::vector<Lease6> getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+                                              const uint8_t prefix_len,
+                                              const uint8_t delegated_len) const;
+
+    /// @brief Checks if client has lease for the specified address.
+    ///
+    /// @param address Address for which lease should be found.
+    ///
+    /// @return true if client has lease for the address, false otherwise.
+    bool hasLeaseForAddress(const asiolink::IOAddress& address) const;
+
+    /// @brief Checks if client has a lease for an address within range.
+    ///
+    /// @param first Lower bound of the address range.
+    /// @param last Upper bound of the address range.
+    ///
+    /// @return true if client has lease for the address within the range,
+    /// false otherwise.
+    bool hasLeaseForAddressRange(const asiolink::IOAddress& first,
+                                 const asiolink::IOAddress& last) const;
+
+    /// @brief Checks if client has a lease for a prefix.
+    ///
+    /// @param prefix Prefix.
+    /// @param prefix_len Prefix length.
+    ///
+    /// @return true if client has a lease for the specified prefix, false
+    /// otherwise.
+    bool hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+                           const uint8_t prefix_len) const;
+
+    /// @brief Checks if client has a lease belonging to a prefix pool.
+    ///
+    /// @param prefix Pool prefix.
+    /// @param prefix_len Prefix length.
+    /// @param delegated_len Delegated prefix length.
+    ///
+    /// @return true if client has a lease belonging to specified pool,
+    /// false otherwise.
+    bool hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+                               const uint8_t prefix_len,
+                               const uint8_t delegated_len) const;
+
     /// @brief Returns the value of the global status code for the last
     /// transaction.
     uint16_t getStatusCode() const {

+ 273 - 0
src/bin/dhcp6/tests/host_unittest.cc

@@ -9,6 +9,10 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/lexical_cast.hpp>
+#include <list>
+#include <sstream>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -107,12 +111,90 @@ const char* CONFIGS[] = {
         "        \"ip-addresses\": [ \"2001:db8:1::2\" ]"
         "    } ]"
         " } ]"
+    "}",
+
+    // Configuration 3:
+    "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+        "    \"interface\" : \"eth0\","
+        "    \"reservations\": ["
+        "    {"
+        "        \"duid\": \"01:02:03:04\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::1\", \"2001:db8:1:1::2\","
+                                    "\"2001:db8:1:1::3\" ],"
+        "        \"prefixes\": [  \"3000:1:1::/32\", \"3000:1:2::/32\","
+                                 "\"3000:1:3::/32\" ]"
+        "    } ]"
+        " } ]"
     "}"
 };
 
+class Reservation {
+public:
+    Reservation(const std::string& resource);
+
+    bool isEmpty() const;
+
+    bool isPrefix() const;
+
+    static const Reservation& UNSPEC();
+
+    operator std::string() const;
+
+private:
+    IOAddress prefix_;
+    uint8_t prefix_len_;
+};
+
+Reservation::Reservation(const std::string& resource)
+    : prefix_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_len_(0) {
+    size_t slash_pos = resource.find("/");
+    if ((slash_pos != std::string::npos) && (slash_pos < resource.size() - 1)) {
+        prefix_len_ = boost::lexical_cast<unsigned int>(resource.substr(slash_pos + 1));
+    }
+    prefix_ = IOAddress(resource.substr(0, slash_pos));
+}
+
+bool
+Reservation::isEmpty() const {
+    return (prefix_.isV6Zero());
+}
+
+bool
+Reservation::isPrefix() const {
+    return (!isEmpty() && (prefix_len_ > 0));
+}
+
+const Reservation& Reservation::UNSPEC() {
+    static Reservation unspec("::/0");
+    return (unspec);
+}
+
+Reservation::operator std::string() const {
+    std::ostringstream s;
+    s << "\"" << prefix_;
+    if (prefix_len_ > 0) {
+        s << "/" << static_cast<int>(prefix_len_);
+    }
+    s << "\"";
+    return (s.str());
+}
+
 /// @brief Test fixture class for testing host reservations
 class HostTest : public Dhcpv6SrvTest {
 public:
+
+
     /// @brief Constructor.
     ///
     /// Sets up fake interfaces.
@@ -153,10 +235,98 @@ public:
         EXPECT_EQ(exp_ip_address, lease_client.addr_.toText());
     }
 
+    static void storeReservation(const Reservation& r,
+                                 std::list<std::string>& address_list,
+                                 std::list<std::string>& prefix_list);
+
+    std::string configString(const DUID& duid,
+                             const Reservation& r1 = Reservation::UNSPEC(),
+                             const Reservation& r2 = Reservation::UNSPEC(),
+                             const Reservation& r3 = Reservation::UNSPEC(),
+                             const Reservation& r4 = Reservation::UNSPEC(),
+                             const Reservation& r5 = Reservation::UNSPEC(),
+                             const Reservation& r6 = Reservation::UNSPEC()) const;
+
     /// @brief Interface Manager's fake configuration control.
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 
+void
+HostTest::storeReservation(const Reservation& r,
+                           std::list<std::string>& address_list,
+                           std::list<std::string>& prefix_list) {
+    if (!r.isEmpty()) {
+        if (r.isPrefix()) {
+            prefix_list.push_back(r);
+        } else {
+            address_list.push_back(r);
+        }
+    }
+}
+
+std::string
+HostTest::configString(const DUID& duid,
+                       const Reservation& r1, const Reservation& r2,
+                       const Reservation& r3, const Reservation& r4,
+                       const Reservation& r5, const Reservation& r6) const {
+    std::list<std::string> address_list;
+    std::list<std::string> prefix_list;
+    storeReservation(r1, address_list, prefix_list);
+    storeReservation(r2, address_list, prefix_list);
+    storeReservation(r3, address_list, prefix_list);
+    storeReservation(r4, address_list, prefix_list);
+    storeReservation(r5, address_list, prefix_list);
+    storeReservation(r6, address_list, prefix_list);
+
+    std::ostringstream s;
+    s << "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+        "    \"pd-pools\": [ { \"prefix\": \"3001::\", \"prefix-len\": 32,"
+        "                      \"delegated-len\": 64 } ],"
+        "    \"interface\" : \"eth0\"";
+
+    if (!address_list.empty() || !prefix_list.empty()) {
+        s << ","
+            "    \"reservations\": ["
+            "    {"
+            "        \"duid\": ";
+        s << "\"" << duid.toText() << "\",";
+
+        if (!address_list.empty()) {
+            s << "        \"ip-addresses\": [ "
+              << boost::algorithm::join(address_list, ", ")
+              << "]";
+        }
+
+        if (!prefix_list.empty()) {
+            if (!address_list.empty()) {
+                s << ", ";
+            }
+            s << "        \"prefixes\": [ "
+              << boost::algorithm::join(prefix_list, ", ")
+              << "]";
+        }
+
+        s <<  "    } ]";
+    }
+
+    s << " } ]"
+         "}";
+
+    return (s.str());
+}
+
+
 // Test basic SARR scenarios against a server configured with one subnet
 // containing two reservations.  One reservation with a hostname, one
 // without a hostname. Scenarios:
@@ -376,4 +546,107 @@ TEST_F(HostTest, hostIdentifiersOrder) {
     testReservationByIdentifier(client, 2, "2001:db8:1::2");
 }
 
+TEST_F(HostTest, reservationMultipleIASolicit) {
+    Dhcp6Client client;
+    client.setDUID("01:02:03:04");
+
+    const std::string c = configString(*client.getDuid(),
+                                       Reservation("2001:db8:1:1::1"),
+                                       Reservation("2001:db8:1:1::2"),
+                                       Reservation("2001:db8:1:1::3"),
+                                       Reservation("3000:1:1::/32"),
+                                       Reservation("3000:1:2::/32"),
+                                       Reservation("3000:1:3::/32"));
+
+    ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+    client.requestAddress(1234);
+    client.requestAddress(2345);
+    client.requestAddress(3456);
+    client.requestPrefix(5678);
+    client.requestPrefix(6789);
+    client.requestPrefix(7890);
+
+    // Send Solicit and require that the client saves received configuration
+    // so as we can test that advertised configuration is correct.
+    ASSERT_NO_THROW(client.doSolicit(true));
+
+    ASSERT_EQ(6, client.getLeaseNum());
+
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+}
+
+TEST_F(HostTest, reservationMultipleIARequest) {
+    Dhcp6Client client;
+    client.setDUID("01:02:03:04");
+
+    const std::string c = configString(*client.getDuid(),
+                                       Reservation("2001:db8:1:1::1"),
+                                       Reservation("2001:db8:1:1::2"),
+                                       Reservation("2001:db8:1:1::3"),
+                                       Reservation("3000:1:1::/32"),
+                                       Reservation("3000:1:2::/32"),
+                                       Reservation("3000:1:3::/32"));
+
+    ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+    client.requestAddress(1234);
+    client.requestAddress(2345);
+    client.requestAddress(3456);
+    client.requestPrefix(5678);
+    client.requestPrefix(6789);
+    client.requestPrefix(7890);
+
+    ASSERT_NO_THROW(client.doSARR());
+
+    ASSERT_EQ(6, client.getLeaseNum());
+
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+}
+
+TEST_F(HostTest, reservationAndDynamicIAs) {
+    Dhcp6Client client;
+    client.setDUID("01:02:03:04");
+
+    const std::string c = configString(*client.getDuid(),
+                                       Reservation("2001:db8:1:1::2"),
+                                       Reservation("2001:db8:1:1::3"),
+                                       Reservation("3000:1:1::/32"),
+                                       Reservation("3000:1:3::/32"));
+
+    ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+    client.requestAddress(1234);
+    client.requestAddress(2345);
+    client.requestAddress(3456);
+    client.requestPrefix(5678);
+    client.requestPrefix(6789);
+    client.requestPrefix(7890);
+
+    // Send Solicit and require that the client saves received configuration
+    // so as we can test that advertised configuration is correct.
+    ASSERT_NO_THROW(client.doSolicit(true));
+
+    ASSERT_EQ(6, client.getLeaseNum());
+
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+    EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+    EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+
+    EXPECT_TRUE(client.hasLeaseForAddressRange(IOAddress("2001:db8:1::1"),
+                                               IOAddress("2001:db8:1::10")));
+    EXPECT_TRUE(client.hasLeaseForPrefixPool(IOAddress("3001::"), 32, 64));
+}
+
 } // end of anonymous namespace

+ 7 - 1
src/lib/dhcpsrv/alloc_engine.cc

@@ -762,10 +762,16 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
     // Get the IPv6 reservations of specified type.
     const IPv6ResrvRange& reservs = ctx.host_->getIPv6Reservations(type);
     for (IPv6ResrvIterator resv = reservs.first; resv != reservs.second; ++resv) {
-        // We do have a reservation for addr.
+        // We do have a reservation for address or prefix.
         IOAddress addr = resv->second.getPrefix();
         uint8_t prefix_len = resv->second.getPrefixLen();
 
+        // We have allocated this address/prefix while processing one of the
+        // previous IAs, so let's try another reservation.
+        if (ctx.isAllocated(addr, prefix_len)) {
+            continue;
+        }
+
         // Check if already have this lease on the existing_leases list.
         for (Lease6Collection::iterator l = existing_leases.begin();
              l != existing_leases.end(); ++l) {