]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2436] Implemement Memfile_LeaseMgr:checkLimitsX functions
authorThomas Markwalder <tmark@isc.org>
Mon, 27 Jun 2022 14:14:08 +0000 (10:14 -0400)
committerThomas Markwalder <tmark@isc.org>
Wed, 29 Jun 2022 11:07:48 +0000 (07:07 -0400)
src/lib/dhcpsrv/lease_mgr.h
    Make LeaseMgr::getClassLeaseCount() const

src/lib/dhcpsrv/memfile_lease_limits.*
    Add ClassLeaseCounter()::getConstCountMap()
    make  size() const

src/lib/dhcpsrv/memfile_lease_mgr.*
    make Memfile_LeaseMgr::getClassLeaseCount() const
    Memfile_LeaseMgr::checkLimits4()
    Memfile_LeaseMgr::checkLimits6()
    Memfile_LeaseMgr::getSubnetStat()
    Memfile_LeaseMgr::getLeaseLimit() - new functions

src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
    GenericLeaseMgrTest::testLeaseLimits4()
    GenericLeaseMgrTest::testLeaseLimits6()
    - use CfgMgr to update subent stats

src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
    Enable checkLimits tests

src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/memfile_lease_limits.cc
src/lib/dhcpsrv/memfile_lease_limits.h
src/lib/dhcpsrv/memfile_lease_mgr.cc
src/lib/dhcpsrv/memfile_lease_mgr.h
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

index d1336b0b6a919631871aa053cfb2e35d3f08bdde..b20ab2d1004a41ff52d7865f80fbd0691eb4eed1 100644 (file)
@@ -801,7 +801,7 @@ public:
     /// @return count of leases
     /// @throw NotImplemented if a derivation does not override this.
     virtual size_t getClassLeaseCount(const ClientClass& client_class,
-                                      const Lease::Type& ltype = Lease::TYPE_V4) {
+                                      const Lease::Type& ltype = Lease::TYPE_V4) const {
         // For now we throw, ultimately this should be pure virtual.
         isc_throw(NotImplemented, "LeaseMgr::getClassLeaseCount "
                   << client_class << ":" << ltype);
index 9cb349a6ff24c122af37f602ef82deae3bf2d227..b1ba336921aea11ec97db32c888c42b8c8a812fc 100644 (file)
@@ -15,8 +15,8 @@ namespace dhcp {
 
 size_t
 ClassLeaseCounter::getClassCount(const ClientClass& client_class,
-                                 const Lease::Type& ltype) {
-    ClassCountMap& leases_by_class = getCountMap(ltype);
+                                 const Lease::Type& ltype) const {
+    const ClassCountMap& leases_by_class = getConstCountMap(ltype);
     auto it = leases_by_class.find(client_class);
     if (it == leases_by_class.end()) {
         return (0);
index 31a3a761a3ce08624193d6a1832d7727c7fc1e35..baa0583adf95e954edcacbf6ef2d38bba5e5d2ce 100644 (file)
@@ -47,7 +47,7 @@ public:
     /// @return Number of leases for the class and lease type.  If there is no
     /// entry found for the class and lease type, a value of zero is returned.
     size_t getClassCount(const ClientClass& client_class,
-                         const Lease::Type& ltype = Lease::TYPE_V4);
+                         const Lease::Type& ltype = Lease::TYPE_V4) const;
 
     /// @brief Sets the lease count for the given class and lease type to a value.
     ///
@@ -120,8 +120,8 @@ public:
     /// defaults to Lease::TYPE_V4
     ///
     /// @return Number of entries for the lease type
-    size_t size(const Lease::Type& ltype = Lease::TYPE_V4) {
-        return (getCountMap(ltype).size());
+    size_t size(const Lease::Type& ltype = Lease::TYPE_V4) const {
+        return (getConstCountMap(ltype).size());
     }
 
     /// @brief Fetches the list of classes from the lease's user-context
@@ -142,6 +142,10 @@ private:
         return (ltype == Lease::TYPE_PD ? pds_by_class_ : addresses_by_class_);
     }
 
+    const ClassCountMap& getConstCountMap(const Lease::Type& ltype = Lease::TYPE_V4) const {
+        return (ltype == Lease::TYPE_PD ? pds_by_class_ : addresses_by_class_);
+    }
+
     /// @brief Contains counts for classes for addresses.  This map is used
     /// to house either Lease::TYPE_V4 when used for V4 or Lease::TYPE_NA
     /// when used for V6.
index 8ac7bd91d993924c78747f0c00547cd6c4aff09f..a69bcaca111ae2cc78da7444e867e5eaa24364c9 100644 (file)
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/timer_mgr.h>
 #include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
 #include <util/multi_threading_mgr.h>
 #include <util/pid_file.h>
+
 #include <cstdio>
 #include <cstring>
 #include <errno.h>
@@ -37,10 +39,10 @@ const char* KEA_LFC_EXECUTABLE_ENV_NAME = "KEA_LFC_EXECUTABLE";
 }  // namespace
 
 using namespace isc::asiolink;
+using namespace isc::data;
 using namespace isc::db;
 using namespace isc::util;
-
-using isc::data::ConstElementPtr;
+using namespace isc::stats;
 
 namespace isc {
 namespace dhcp {
@@ -2089,7 +2091,6 @@ Memfile_LeaseMgr::wipeLeases6(const SubnetID& subnet_id) {
     return (num);
 }
 
-
 void
 Memfile_LeaseMgr::recountClassLeases4() {
     class_lease_counter_.clear();
@@ -2114,7 +2115,7 @@ Memfile_LeaseMgr::recountClassLeases6() {
 
 size_t
 Memfile_LeaseMgr::getClassLeaseCount(const ClientClass& client_class,
-                                     const Lease::Type& ltype /* = Lease::TYPE_V4*/) {
+                                     const Lease::Type& ltype /* = Lease::TYPE_V4*/) const {
     return(class_lease_counter_.getClassCount(client_class, ltype));
 }
 
@@ -2124,13 +2125,190 @@ Memfile_LeaseMgr::clearClassLeaseCounts() {
 }
 
 std::string
-Memfile_LeaseMgr::checkLimits4(ConstElementPtr const& /* user_context */) const {
-    isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits4() not implemented");
+Memfile_LeaseMgr::checkLimits4(isc::data::ConstElementPtr const& user_context) const {
+    if (!user_context) {
+        return ("");
+    }
+
+    ConstElementPtr limits = user_context->find("ISC/limits");
+    if (!limits) {
+        return ("");
+    }
+
+    // Iterate of the 'client-classes' list in 'limits'. For each class that specifies
+    // an "address-limit", check its value against the class's lease count.
+    ConstElementPtr classes = limits->get("client-classes");
+    if (classes) {
+        for (int i = 0; i < classes->size(); ++i) {
+            ConstElementPtr class_elem = classes->get(i);
+            // Get class name.
+            ConstElementPtr name_elem = class_elem->get("name");
+            if (!name_elem) {
+                isc_throw(BadValue, "checkLimits4 - client-class.name is missing: "
+                          << prettyPrint(limits));
+            }
+
+            std::string name = name_elem->stringValue();
+
+            // Now look for an address-limit
+            size_t limit;
+            if (!getLeaseLimit(class_elem, Lease::TYPE_V4, limit)) {
+                // No limit, go to the next class.
+                continue;
+            }
+
+            // If the limit is > 0 look up the class lease count.  Limit of 0 always
+            // denies the lease.
+            size_t lease_count = 0;
+            if (limit) {
+                lease_count = getClassLeaseCount(name);
+            }
+
+            // If we're over the limit, return the error, no need to evaluate any others.
+            if (lease_count >= limit) {
+                std::ostringstream ss;
+                ss << "address limit " << limit << " for client class \""
+                   << name << "\", current lease count " << lease_count;
+                return (ss.str());
+            }
+        }
+    }
+
+    // If there were class limits we passed them, now look for a subnet limit.
+    ConstElementPtr subnet_elem = limits->get("subnet");
+    if (subnet_elem) {
+        // Get the subnet id.
+        ConstElementPtr id_elem = subnet_elem->get("id");
+        if (!id_elem) {
+            isc_throw(BadValue, "checkLimits4 - subnet.id is missing: "
+                      << prettyPrint(limits));
+        }
+
+        SubnetID subnet_id = id_elem->intValue();
+
+        // Now look for an address-limit.
+        size_t limit;
+        if (getLeaseLimit(subnet_elem, Lease::TYPE_V4, limit)) {
+            // If the limit is > 0 look up the subnet lease count. Limit of 0 always
+            // denies the lease.
+            auto lease_count = 0;
+            if (limit) {
+                lease_count = getSubnetStat(subnet_id, "assigned-addresses");
+            }
+
+            // If we're over the limit, return the error.
+            if (lease_count >= limit) {
+                std::ostringstream ss;
+                ss << "address limit " << limit << " for subnet ID " << subnet_id
+                   << ", current lease count " << lease_count;
+                return (ss.str());
+            }
+        }
+    }
+
+    // No limits exceeded!
+    return ("");
 }
 
 std::string
-Memfile_LeaseMgr::checkLimits6(ConstElementPtr const& /* user_context */) const {
-    isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits6() not implemented");
+Memfile_LeaseMgr:: checkLimits6(isc::data::ConstElementPtr const& user_context) const {
+    if (!user_context) {
+        return ("");
+    }
+
+    ConstElementPtr limits = user_context->find("ISC/limits");
+    if (!limits) {
+        return ("");
+    }
+
+    // Iterate over the 'client-classes' list in 'limits'. For each class that specifies
+    // limit (either "address-limit" or "prefix-limit", check its value against the appropriate
+    // class lease count.
+    ConstElementPtr classes = limits->get("client-classes");
+    if (classes) {
+        for (int i = 0; i < classes->size(); ++i) {
+            ConstElementPtr class_elem = classes->get(i);
+            // Get class name.
+            ConstElementPtr name_elem = class_elem->get("name");
+            if (!name_elem) {
+                isc_throw(BadValue, "client-class.name is missing: "
+                          << prettyPrint(limits));
+            }
+
+            std::string name = name_elem->stringValue();
+
+            // Now look for either address-limit or a prefix=limit.
+            size_t limit = 0;
+            Lease::Type ltype = Lease::TYPE_NA;
+            if (!getLeaseLimit(class_elem, ltype, limit)) {
+                ltype = Lease::TYPE_PD;
+                if (!getLeaseLimit(class_elem, ltype, limit)) {
+                    // No limits for this class, skip to the next.
+                    continue;
+                }
+            }
+
+            // If the limit is > 0 look up the class lease count.  Limit of 0 always
+            // denies the lease.
+            size_t lease_count = 0;
+            if (limit) {
+                lease_count = getClassLeaseCount(name, ltype);
+            }
+
+            // If we're over the limit, return the error, no need to evaluate any others.
+            if (lease_count >= limit) {
+                std::ostringstream ss;
+                ss << (ltype == Lease::TYPE_NA ? "address" : "prefix")
+                   << " limit " << limit << " for client class \""
+                   << name << "\", current lease count " << lease_count;
+                return (ss.str());
+            }
+        }
+    }
+
+    // If there were class limits we passed them, now look for a subnet limit.
+    ConstElementPtr subnet_elem = limits->get("subnet");
+    if (subnet_elem) {
+        // Get the subnet id.
+        ConstElementPtr id_elem = subnet_elem->get("id");
+        if (!id_elem) {
+            isc_throw(BadValue, "subnet.id is missing: "
+                      << prettyPrint(limits));
+        }
+
+        SubnetID subnet_id = id_elem->intValue();
+
+        // Now look for either address-limit or a prefix=limit.
+        size_t limit = 0;
+        Lease::Type ltype = Lease::TYPE_NA;
+        if (!getLeaseLimit(subnet_elem, ltype, limit)) {
+            ltype = Lease::TYPE_PD;
+            if (!getLeaseLimit(subnet_elem, ltype, limit)) {
+                // No limits for the subnet so none exceeded!
+                return ("");
+            }
+        }
+
+        // If the limit is > 0 look up the class lease count.  Limit of 0 always
+        // denies the lease.
+        size_t lease_count = 0;
+        if (limit) {
+            lease_count = getSubnetStat(subnet_id, (ltype == Lease::TYPE_NA ?
+                                                    "assigned-nas" : "assigned-pds"));
+        }
+
+        // If we're over the limit, return the error.
+        if (lease_count >= limit) {
+            std::ostringstream ss;
+            ss << (ltype == Lease::TYPE_NA ? "address" : "prefix")
+               << " limit " << limit << " for subnet ID " << subnet_id
+               << ", current lease count " << lease_count;
+            return (ss.str());
+        }
+    }
+
+    // No limits exceeded!
+    return ("");
 }
 
 bool
@@ -2138,5 +2316,37 @@ Memfile_LeaseMgr::isJsonSupported() const {
     return true;
 }
 
+int64_t
+Memfile_LeaseMgr::getSubnetStat(const SubnetID& subnet_id, const std::string& stat_label) const {
+    /// @todo This could be simplified if StatsMgr provided a mechanism to
+    /// return the most recent sample as an InterSample.
+    std::string stat_name = StatsMgr::generateName("subnet", subnet_id, stat_label);
+    ConstElementPtr stat = StatsMgr::instance().get(stat_name);
+    ConstElementPtr samples = stat->get(stat_name);
+    if (samples && samples->size()) {
+        auto sample = samples->get(0);
+        if (sample->size()) {
+            auto count_elem = sample->get(0);
+            return (count_elem->intValue());
+        }
+    }
+
+    return (0);
+}
+
+bool
+Memfile_LeaseMgr::getLeaseLimit(ConstElementPtr parent, Lease::Type ltype, size_t& limit) const {
+    ConstElementPtr limit_elem = parent->get(ltype == Lease::TYPE_PD ?
+                                             "prefix-limit" : "address-limit");
+    if (limit_elem) {
+        limit = limit_elem->intValue();
+        return (true);
+    }
+
+    return (false);
+}
+
+
+
 }  // namespace dhcp
 }  // namespace isc
index 724c492a2ef75c4ed48d41e5f3ee6775500a7c0e..5b77a92e4c5d6682be3f99fa0cc71115e7815dca 100644 (file)
@@ -840,6 +840,26 @@ private:
                                           StorageType& storage,
                                           LeaseFileType& lease_file) const;
 
+    /// @brief Fetches the most recent value for a subnet statistic
+    ///
+    /// @param subnet_id subnet id of the subnet for which the stat is desired
+    /// @param stat_label name of the statisitic desired (e.g. "assigned-addresses")
+    ///
+    /// @return Value of the statistic or zero if there are no entries found.
+    int64_t getSubnetStat(const SubnetID& subnet_id, const std::string& stat_label) const;
+
+    /// @brief Fetches the integer value of lease limit element from a parent element based
+    /// on Lease::Type.
+    ///
+    /// @param parent parent element (e.g. "client-class" or "subnet") in which to look for
+    /// the limit
+    /// @param ltype Lease::Type of the limit for which to look (one of Lease::TYPE_V4,
+    /// Lease::TYPE_NA, or Lease::TYPE_PD)
+    /// @param[out] limit contains the value of the limit if found
+    ///
+    /// @return bool true if a limit for the lease type was found, false otherwise.
+    bool getLeaseLimit(data::ConstElementPtr parent, Lease::Type ltype, size_t& limit) const;
+
 public:
 
     /// @brief Return backend type
@@ -1225,7 +1245,7 @@ public:
     /// @param ltype type of lease for which the count is desired. Defaults to
     /// Lease::TYPE_V4.
     size_t getClassLeaseCount(const ClientClass& client_class,
-                              const Lease::Type& ltype = Lease::TYPE_V4) override;
+                              const Lease::Type& ltype = Lease::TYPE_V4) const override;
 
     /// @brief Recount the leases per class for V4 leases.
     ///
index b42828b682721122663ff1a3e029c6002e1f315a..01a83dff279d22782f9d9a5e47a36249ba922f8f 100644 (file)
@@ -3906,6 +3906,15 @@ GenericLeaseMgrTest::testLeaseStatsQueryAttribution6() {
 
 void
 GenericLeaseMgrTest::testLeaseLimits4() {
+    // Create a subnet.  We need the subnet to exist so statistics will exist.
+    CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    Subnet4Ptr subnet;
+
+    subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+    cfg->add(subnet);
+
+    ASSERT_NO_THROW(CfgMgr::instance().commit());
+
     std::string text;
     ElementPtr user_context;
 
@@ -3938,6 +3947,9 @@ GenericLeaseMgrTest::testLeaseLimits4() {
     makeLease4("192.0.1.1", 1, Lease::STATE_DEFAULT, Element::fromJSON(
         R"({ "ISC": { "client-classes": [ "foo" ] } })"));
 
+    // Since we did not go through allocation engine stats won't be altered.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
     user_context = Element::fromJSON(R"({ "ISC": { "limits": {
         "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
     ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
@@ -3951,6 +3963,15 @@ GenericLeaseMgrTest::testLeaseLimits4() {
 
 void
 GenericLeaseMgrTest::testLeaseLimits6() {
+    // Create a subnet.  We need the subnet to exist so statistics will exist.
+    CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    Subnet4Ptr subnet;
+
+    subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+    cfg->add(subnet);
+
+    ASSERT_NO_THROW(CfgMgr::instance().commit());
+
     std::string text;
     ElementPtr user_context;
 
@@ -3985,6 +4006,9 @@ GenericLeaseMgrTest::testLeaseLimits6() {
     makeLease6(Lease::TYPE_PD, "2001:db8:1::", 64, 1, Lease::STATE_DEFAULT, Element::fromJSON(
         R"({ "ISC": { "client-classes": [ "foo" ] } })"));
 
+    // Since we did not go through allocation engine stats won't be altered.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats6());
+
     user_context = Element::fromJSON(R"({ "ISC": { "limits": {
         "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
     ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
index 87d830dfd6cd8979d8a54acc2c6b8e6e38f08f57..07795b9e6a992cf91530485ee9c07755f62eaaad 100644 (file)
@@ -2621,8 +2621,7 @@ TEST_F(MemfileLeaseMgrTest, classLeaseCount6_PD) {
 }
 
 // brief Checks that a null user context allows allocation.
-// DISABLED_ until Memfile_LeaseMgr implements checkLimits4().
-TEST_F(MemfileLeaseMgrTest, DISABLED_checkLimitsNull4) {
+TEST_F(MemfileLeaseMgrTest, checkLimitsNull4) {
     startBackend(V4);
     std::string text;
     ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(nullptr));
@@ -2639,15 +2638,13 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_checkLimitsNull6) {
 }
 
 // Checks a few V4 lease limit checking scenarios.
-// Disabbled until Memfile_LeaseMgr implements checkLimits4() function.
-TEST_F(MemfileLeaseMgrTest, DISABLED_checkLimits4) {
+TEST_F(MemfileLeaseMgrTest, checkLimits4) {
     startBackend(V4);
     testLeaseLimits4();
 }
 
 // Checks a few V4 lease limit checking scenarios.
-// Disabbled until Memfile_LeaseMgr implements checkLimits4() function.
-TEST_F(MemfileLeaseMgrTest, DISABLED_checkLimits6) {
+TEST_F(MemfileLeaseMgrTest, checkLimits6) {
     startBackend(V6);
     testLeaseLimits6();
 }