]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2444] add checkLimitsX() methods to LeaseMgr
authorAndrei Pavel <andrei@isc.org>
Tue, 21 Jun 2022 14:27:22 +0000 (17:27 +0300)
committerAndrei Pavel <andrei@isc.org>
Tue, 21 Jun 2022 18:18:46 +0000 (18:18 +0000)
src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/memfile_lease_mgr.cc
src/lib/dhcpsrv/memfile_lease_mgr.h
src/lib/dhcpsrv/mysql_lease_mgr.cc
src/lib/dhcpsrv/mysql_lease_mgr.h
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/pgsql_lease_mgr.h
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc

index 261780d37558f00350e5369b5628498c12936b8d..6f19c6348502e695dad2881207815f0ef4976e19 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_service.h>
+#include <cc/data.h>
 #include <database/db_exceptions.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
@@ -697,6 +698,32 @@ public:
     /// @return number of leases removed.
     virtual size_t wipeLeases6(const SubnetID& subnet_id) = 0;
 
+    /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+    /// Abstract method.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    virtual std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const = 0;
+
+    /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+    /// Abstract method.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    virtual std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const = 0;
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
index 8ea5feab92409de2d8ed476fc83bbaae06b2f6d2..f1337b5bcb93eb381ec9a6194716e6a2cd1dadff 100644 (file)
@@ -40,6 +40,8 @@ using namespace isc::asiolink;
 using namespace isc::db;
 using namespace isc::util;
 
+using isc::data::ConstElementPtr;
+
 namespace isc {
 namespace dhcp {
 
@@ -2057,6 +2059,16 @@ Memfile_LeaseMgr::wipeLeases6(const SubnetID& subnet_id) {
     return (num);
 }
 
+std::string
+Memfile_LeaseMgr::checkLimits4(ConstElementPtr const& user_context) const {
+    isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits4() not implemented");
+}
+
+std::string
+Memfile_LeaseMgr::checkLimits6(ConstElementPtr const& user_context) const {
+    isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits4() not implemented");
+}
+
 }  // namespace dhcp
 }  // namespace isc
 
index ec277324e473a2b637f06d4ef101165ebdd0ffd2..15be0cb564c06961f51fb4efea999293763fcab9 100644 (file)
@@ -504,6 +504,32 @@ public:
     /// @return number of leases removed.
     virtual size_t wipeLeases6(const SubnetID& subnet_id);
 
+    /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+    /// Memfile implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+    /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+    /// Memfile implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
 private:
 
     /// @name Internal methods called while holding the mutex in multi threading
index 5717b760a329d3b3c5c66ceb6e898c6b3ab1f93d..e71c252c65e21dff954197a4d3097fb8727c0a0a 100644 (file)
@@ -325,9 +325,11 @@ tagged_statements = { {
                     "SELECT subnet_id, lease_type, state, leases as state_count "
                         "FROM lease6_stat "
                         "WHERE subnet_id >= ? and subnet_id <= ? "
-                        "ORDER BY subnet_id, lease_type, state"}
-    }
-};
+                        "ORDER BY subnet_id, lease_type, state"},
+    // TODO: remove single quotes from the following two SELECTs when the functions are implemented
+    {MySqlLeaseMgr::CHECK_LEASE4_LIMITS, "SELECT 'checkLease4Limits(?)'"},
+    {MySqlLeaseMgr::CHECK_LEASE6_LIMITS, "SELECT 'checkLease6Limits(?)'"},
+} };  // tagged_statements
 
 }  // namespace
 
@@ -3091,6 +3093,95 @@ MySqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
     return (deleted_leases);
 }
 
+string
+MySqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const {
+    constexpr int column_count(1);
+
+    // Set up the WHERE clause value.
+    MYSQL_BIND inbind[column_count];
+    memset(inbind, 0, sizeof(inbind));
+    std::string const& user_context_string(user_context->str());
+    inbind[0].buffer = const_cast<char*>(user_context_string.c_str());
+    inbind[0].buffer_length = user_context_string.length();
+    inbind[0].buffer_type = MYSQL_TYPE_STRING;
+
+    my_bool error[column_count];
+    MySqlLeaseExchange::setErrorIndicators(inbind, error, column_count);
+
+    // Get a context and a prepared statement.
+    MySqlLeaseContextAlloc get_context(*this);
+    MySqlLeaseContextPtr ctx = get_context.ctx_;
+    MYSQL_STMT* prepared_statement(ctx->conn_.statements_[stindex]);
+
+    // Bind the selection parameters to the statement.
+    int status(mysql_stmt_bind_param(prepared_statement, inbind));
+    checkError(ctx, status, stindex, "unable to bind WHERE clause parameter");
+
+    // Set up the MYSQL_BIND array for the data being returned and bind it to
+    // the statement.
+    std::vector<MYSQL_BIND> outbind;
+    outbind.push_back(MYSQL_BIND());
+    my_bool result_null(MLM_FALSE);
+    char result[USER_CONTEXT_MAX_LEN];
+    unsigned long result_length(sizeof(result));
+    outbind[0].buffer_type = MYSQL_TYPE_STRING;
+    outbind[0].buffer = reinterpret_cast<char*>(result);
+    outbind[0].buffer_length = result_length;
+    outbind[0].length = &result_length;
+    outbind[0].is_null = &result_null;
+    status = mysql_stmt_bind_result(prepared_statement, &outbind[0]);
+    checkError(ctx, status, stindex, "unable to bind SELECT clause parameters");
+
+    // Execute the statement.
+    status = MysqlExecuteStatement(prepared_statement);
+    checkError(ctx, status, stindex, "unable to execute");
+
+    // Ensure that all the lease information is retrieved in one go to avoid
+    // overhead of going back and forth between client and server.
+    status = mysql_stmt_store_result(prepared_statement);
+    checkError(ctx, status, stindex, "unable to set up for storing all results");
+
+    // Set up the fetch "release" object to release resources associated
+    // with the call to mysql_stmt_fetch when this method exits, then
+    // retrieve the data.
+    MySqlFreeResult fetch_release(prepared_statement);
+    int count = 0;
+    while ((status = mysql_stmt_fetch(prepared_statement)) == 0) {
+        // "result" should be populated now. We'll use that directly outside the loop.
+
+        if (++count > 1) {
+            isc_throw(MultipleRecords, "multiple records were found in the "
+                      "database where only one was expected for query "
+                      << ctx->conn_.text_statements_[stindex]);
+        }
+    }
+
+    // How did the fetch end?
+    if (status == 1) {
+        // Error - unable to fetch results
+        checkError(ctx, status, stindex, "unable to fetch results");
+    } else if (status == MYSQL_DATA_TRUNCATED) {
+        // Data truncated - throw an exception indicating what was at fault.
+        std::string columns[column_count];
+        columns[0] = "limits";
+        isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex]
+                  << " returned truncated data: columns affected are "
+                  << MySqlLeaseExchange::getColumnsInError(error, columns, column_count));
+    }
+
+    return result;
+}
+
+string
+MySqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const {
+    return checkLimits(user_context, CHECK_LEASE4_LIMITS);
+}
+
+string
+MySqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const {
+    return checkLimits(user_context, CHECK_LEASE6_LIMITS);
+}
+
 LeaseStatsQueryPtr
 MySqlLeaseMgr::startLeaseStatsQuery4() {
     // Get a context
index 5a53ef644b08aeb3b8fa6e1f9998e04b78d31207..562310284177405a4ae74991db3772afc8306069 100644 (file)
@@ -724,6 +724,8 @@ public:
         ALL_LEASE6_STATS,            // Fetches IPv6 lease statistics
         SUBNET_LEASE6_STATS,         // Fetched IPv6 lease stats for a single subnet.
         SUBNET_RANGE_LEASE6_STATS,   // Fetched IPv6 lease stats for a subnet range.
+        CHECK_LEASE4_LIMITS,         // Check if allocated IPv4 leases are inside the set limits.
+        CHECK_LEASE6_LIMITS,         // Check if allocated IPv6 leases are inside the set limits.
         NUM_STATEMENTS               // Number of statements
     };
 
@@ -925,6 +927,46 @@ private:
     uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
                                                 StatementIndex statement_index);
 
+    /// @brief Checks if the lease limits set in the given user context are exceeded.
+    /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string
+    checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const;
+
+    /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+    /// MySQL implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+    /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+    /// MySQL implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
     /// @brief Check Error and Throw Exception
     ///
     /// This method invokes @ref MySqlConnection::checkError.
index 2470c26a04ac2d25298a3345983d3a5ef1d70828..e702324f0a787acfaeb46415ba51a525843e9ca0 100644 (file)
@@ -358,6 +358,19 @@ PgSqlTaggedStatement tagged_statements[] = {
       "  FROM lease6_stat "
       "  WHERE subnet_id >= $1 and subnet_id <= $2 "
       "  ORDER BY subnet_id, lease_type, state" },
+
+    // TODO: remove single quotes from the following two SELECTs when the functions are implemented
+
+    // CHECK_LEASE4_LIMITS
+    { 1, { OID_TEXT },
+      "check_lease4_limits",
+      "SELECT 'checkLease4Limits($1)'" },
+
+    // CHECK_LEASE6_LIMITS
+    { 1, { OID_TEXT },
+      "check_lease6_limits",
+      "SELECT 'checkLease6Limits($1)'" },
+
     // End of list sentinel
     { 0,  { 0 }, NULL, NULL}
 };
@@ -2275,6 +2288,47 @@ PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
     return (deleteLeaseCommon(statement_index, bind_array));
 }
 
+string
+PgSqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const {
+    // Bindings
+    PsqlBindArray bind_array;
+    std::string const user_context_str(user_context->str());
+    bind_array.add(user_context_str);
+
+    // Get a context.
+    PgSqlLeaseContextAlloc get_context(*this);
+    PgSqlLeaseContextPtr ctx(get_context.ctx_);
+
+    PgSqlResult r(PQexecPrepared(ctx->conn_,
+                                 tagged_statements[stindex].name,
+                                 tagged_statements[stindex].nbparams,
+                                 &bind_array.values_[0],
+                                 &bind_array.lengths_[0],
+                                 &bind_array.formats_[0], 0));
+
+    ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+    int rows = PQntuples(r);
+    if (rows > 1) {
+        isc_throw(MultipleRecords, "multiple records were found in the "
+                      "database where only one was expected for query "
+                      << tagged_statements[stindex].name);
+    }
+
+    std::string const limits(PgSqlExchange::getRawColumnValue(r, 0, 0));
+    return limits;
+}
+
+string
+PgSqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const {
+    return checkLimits(user_context, CHECK_LEASE4_LIMITS);
+}
+
+string
+PgSqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const {
+    return checkLimits(user_context, CHECK_LEASE6_LIMITS);
+}
+
 LeaseStatsQueryPtr
 PgSqlLeaseMgr::startLeaseStatsQuery4() {
     // Get a context
index 4268f6ca181057b1c61d5bb359a46230bc23953e..3abd14c9d1a76a3669feeae5452267982cb4dbd2 100644 (file)
@@ -699,6 +699,8 @@ public:
         ALL_LEASE6_STATS,            // Fetches IPv6 lease statistics
         SUBNET_LEASE6_STATS,         // Fetched IPv6 lease stats for a single subnet.
         SUBNET_RANGE_LEASE6_STATS,   // Fetched IPv6 lease stats for a subnet range.
+        CHECK_LEASE4_LIMITS,         // Check if allocated IPv4 leases are inside the set limits.
+        CHECK_LEASE6_LIMITS,         // Check if allocated IPv6 leases are inside the set limits.
         NUM_STATEMENTS               // Number of statements
     };
 
@@ -899,6 +901,46 @@ private:
     uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
                                                 StatementIndex statement_index);
 
+    /// @brief Checks if the lease limits set in the given user context are exceeded.
+    /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string
+    checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const;
+
+    /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+    /// PostgreSQL implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+    /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+    /// MySQL implementation.
+    ///
+    /// @param user_context all or part of the lease's user context which, for the intents and
+    /// purposes of lease limiting should have the following format
+    /// (not all nodes are mandatory and values are given only as examples):
+    /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+    ///                        "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+    ///
+    /// @return a string describing a limit that is being exceeded, or an empty
+    /// string if no limits are exceeded
+    std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
     /// @brief Context RAII Allocator.
     class PgSqlLeaseContextAlloc {
     public:
index fc8dc31f9dbcb9ab3bf12e8127eec0d403702870..0fce71ec0258e6b8d3a7be0c216ae89bf4c775e7 100644 (file)
@@ -361,6 +361,14 @@ public:
         isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases6 not implemented");
     }
 
+    std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override {
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits4() not implemented");
+    }
+
+    std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override{
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits6() not implemented");
+    }
+
     /// @brief Returns backend type.
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)