]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5487] Cleaned up, brute force method
authorThomas Markwalder <tmark@isc.org>
Wed, 24 Jan 2018 15:57:59 +0000 (10:57 -0500)
committerThomas Markwalder <tmark@isc.org>
Wed, 24 Jan 2018 15:57:59 +0000 (10:57 -0500)
src/lib/dhcpsrv/cql_lease_mgr.cc
    CqlLeaseStatsQuery - remove derivation from CqlExchange

    start() - moved aggregation logic into executeSelect

    executeSelect() - implements explicitly so the raw CQL rows
    are aggregated directly into the final result set map

src/lib/dhcpsrv/cql_lease_mgr.cc
src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc

index ca87638aa8458995b2593e24f327073892f6cb06..e40638e514557edd2441acccb00caad595358fe6 100644 (file)
@@ -1443,19 +1443,19 @@ CqlLease6Exchange::getExpiredLeases(const size_t &max_leases,
 /// This class provides the functionality such as results storage and row
 /// fetching common to fulfilling the statistical lease data query.
 ///
-class CqlLeaseStatsQuery : public LeaseStatsQuery, CqlExchange {
+class CqlLeaseStatsQuery : public LeaseStatsQuery {
 public:
     /// @brief Constructor
     ///
     /// @param conn A open connection to the database housing the lease data
     /// @param statement The lease data SQL prepared statement tag to execute
     /// @param fetch_type Indicates whether or not lease_type should be
-    /// fetched from the result set
+    /// fetched from the result set (should be true for v6)
     CqlLeaseStatsQuery(CqlConnection& conn, StatementTag& statement,
                          const bool fetch_type)
         : conn_(conn), statement_(statement), fetch_type_(fetch_type),
           cummulative_rows_(), next_row_(cummulative_rows_.begin()),
-          subnet_id_(0), type_(0), state_(0) {
+          subnet_id_(0), lease_type_(0), lease_state_(0) {
     }
 
     /// @brief Destructor
@@ -1465,27 +1465,38 @@ public:
     ///
     /// The result set is populated by executing a  prepared SQL query
     /// against the database which sums the leases per lease state per
-    /// subnet id.
-    void start() {
-        AnyArray data; // there are no where clause parameters
-
-        // This gets a collection of data for ALL leases, ugh
-        AnyArray collection = executeSelect(conn_, data, statement_);
-
-        // Now we need to coaelesce the raw rows
-        for (boost::any &element : collection) {
-            LeaseStatsRowPtr raw_row = boost::any_cast<LeaseStatsRowPtr>(element);
-            auto cum_row = cummulative_rows_.find(*raw_row);
-            if (cum_row != cummulative_rows_.end()) {
-                cummulative_rows_[*raw_row] = cum_row->second + 1;
-            } else {
-                cummulative_rows_.emplace(std::make_pair(*raw_row, 1));
-            }
-        }
-
-        next_row_ = cummulative_rows_.begin();
-    }
+    /// subnet id.  Positions internal row tracking to point to the
+    /// first row of the aggregate results.
+    void start();
 
+    /// @brief Executes protocol specific lease query SELECT statement
+    ///
+    /// Currently we do not have a good way for Cassandra to roll up the
+    /// lease counts per subnet, type, and state as we do the other back
+    /// ends.  This method executes the select statement which returns
+    /// a result set containing a row of data for every lease:
+    /// -v4 - subnet-id, lease-state
+    /// -v6 - subnet-id, lease-type, lease-state
+    ///
+    /// It then iterates over this result set, aggregating the data into a
+    /// a map of LeaseStatRows.
+    ///
+    /// If we didn't have to roll up the raw lease data first, we could
+    /// have derived this class from CqlExchange and used it's executeSelect
+    /// (from which this method borrows heavily). However, that would mean
+    /// copying all the raw lease  data into a collection returned by
+    /// executeSelect and then aggregating that into cummulative rows.
+    /// The way we are now we go turn the raw lease data directly into the
+    /// cummulative row map.
+    ///
+    /// @param connection connection used to communicate with the Cassandra
+    /// database
+    /// @param where_values array of bound objects used to filter the results
+    /// @param statement_tag prepared statement being executed
+    ///
+    /// @throw DbOperationError
+    void executeSelect(const CqlConnection& connection, const AnyArray& data,
+                       StatementTag statement_tag);
 
     /// @brief Fetches the next row in the result set
     ///
@@ -1498,20 +1509,7 @@ public:
     ///
     /// @return True if the fetch succeeded, false if there are no more
     /// rows to fetch.
-    bool getNextRow(LeaseStatsRow& row) {
-        // If we're past the end, punt.
-        if (next_row_ == cummulative_rows_.end()) {
-            return (false);
-        }
-
-        row = next_row_->first;
-        row.state_count_ = next_row_->second;
-
-
-        // Point to the next row.
-        ++next_row_;
-        return (true);
-    }
+    bool getNextRow(LeaseStatsRow& row);
 
     /// @brief Create BIND array to receive C++ data.
     ///
@@ -1521,16 +1519,8 @@ public:
     /// @param statement_tag prepared statement being executed; defaults to an
     ///     invalid index
     virtual void
-    createBindForSelect(AnyArray& data, StatementTag statement_tag = NULL) override;
+    createBindForSelect(AnyArray& data, StatementTag statement_tag = NULL);
 
-    /// @brief Copy received data into the <version,minor> pair.
-    ///
-    /// Copies information about the version to be retrieved into a pair. Called
-    /// in executeSelect().
-    ///
-    /// @return a pointer to the object retrieved.
-    virtual boost::any retrieve() override;
-    
     /// @brief Statement tags definitions
     /// @{
     // Return recalculated lease4 lease statistics
@@ -1538,7 +1528,7 @@ public:
     // Return recalculated lease6 lease statistics
     static constexpr StatementTag RECOUNT_LEASE6_STATS = "RECOUNT_LEASE6_STATS";
     /// @}
-   
+
     /// @brief Cassandra statements
     static StatementMap tagged_statements_;
 
@@ -1552,67 +1542,187 @@ private:
     /// @brief Indicates if query supplies lease type
     bool fetch_type_;
 
+
+    /// @brief map containing the aggregated lease counts
     std::map<LeaseStatsRow, int> cummulative_rows_;
 
+    /// @brief cursor pointing to the next row to read in aggregate map
     std::map<LeaseStatsRow, int>::iterator next_row_;
 
+    /// @brief bind variable for retrieving subnet-id from a result set row
     int subnet_id_;
-    int type_;
-    int state_;
+    /// @brief bind variable for retrieving lease-type from a result set row
+    int lease_type_;
+    /// @brief bind variable for retrieving lease-state from a result set row
+    int lease_state_;
 };
 
 constexpr StatementTag CqlLeaseStatsQuery::RECOUNT_LEASE4_STATS;
 constexpr StatementTag CqlLeaseStatsQuery::RECOUNT_LEASE6_STATS;
 
 StatementMap CqlLeaseStatsQuery::tagged_statements_{
-    // Return recalculated lease4 lease statistics
-    {RECOUNT_LEASE4_STATS, 
-        {RECOUNT_LEASE4_STATS, 
+    // Return subnet_id and state of each v4 lease
+    {RECOUNT_LEASE4_STATS,
+        {RECOUNT_LEASE4_STATS,
         "SELECT "
         "subnet_id, state "
         "FROM lease4 "
-#if 0
-        "GROUP BY subnet_id, state "
-        "ORDER BY subnet_id"
-#endif
     }},
 
-    // Return recalculated lease6 lease statistics
-    {RECOUNT_LEASE6_STATS, 
-        {RECOUNT_LEASE6_STATS, 
+    // Return subnet_id, lease_type, and state of each v6 lease
+    {RECOUNT_LEASE6_STATS,
+        {RECOUNT_LEASE6_STATS,
         "SELECT "
         "subnet_id, lease_type, state "
         "FROM lease6 "
-#if 0
-        "GROUP BY subnet_id, lease_type, state "
-        "ORDER BY subnet_id"
-#endif
     }},
 };
 
+void
+CqlLeaseStatsQuery::start() {
+    AnyArray data; // there are no where clause parameters
+
+    // This gets a collection of data for ALL leases, and
+    // then rolls them up into cummulative_rows_
+    executeSelect(conn_, data, statement_);
+
+    // Set our row iterator to the beginning
+    next_row_ = cummulative_rows_.begin();
+}
+
+bool
+CqlLeaseStatsQuery::getNextRow(LeaseStatsRow& row) {
+    // If we're past the end, punt.
+    if (next_row_ == cummulative_rows_.end()) {
+        return (false);
+    }
+
+    // Start by copying from the map row key
+    row.subnet_id_ = next_row_->first.subnet_id_;
+    row.lease_type_ = next_row_->first.lease_type_;
+    row.lease_state_ = next_row_->first.lease_state_;
+
+    // Grab the count from the map value
+    row.state_count_ = next_row_->second;
+
+    // Point to the next row.
+    ++next_row_;
+    return (true);
+}
+
 void
 CqlLeaseStatsQuery::createBindForSelect(AnyArray& data, StatementTag) {
     data.clear();
     data.add(&subnet_id_);
     if (fetch_type_) {
-        data.add(&type_);
+        data.add(&lease_type_);
     }
 
-    data.add(&state_);
+    data.add(&lease_state_);
 }
 
-boost::any
-CqlLeaseStatsQuery::retrieve() {
-    LeaseStatsRowPtr raw_row(new LeaseStatsRow());
-    raw_row->subnet_id_ = subnet_id_;
-    if (fetch_type_) {
-        raw_row->lease_type_ = static_cast<Lease::Type>(type_);
+void
+CqlLeaseStatsQuery::executeSelect(const CqlConnection& connection, const AnyArray& data,
+                                  StatementTag statement_tag)  {
+    CassError rc;
+    CassStatement* statement = NULL;
+    CassFuture* future = NULL;
+    AnyArray local_data = data;
+
+    // Find the query statement first.
+    StatementMap::const_iterator it = connection.statements_.find(statement_tag);
+    if (it == connection.statements_.end()) {
+        isc_throw(DbOperationError,
+                  "CqlLeastStatsQuery::executeSelect(): Statement "
+                      << statement_tag << "has not been prepared.");
+    }
+
+    // Bind the data before the query is executed.
+    CqlTaggedStatement tagged_statement = it->second;
+    if (tagged_statement.is_raw_) {
+        // The entire query is the first element in data.
+        std::string* query = boost::any_cast<std::string*>(local_data.back());
+        local_data.pop_back();
+        statement = cass_statement_new(query->c_str(), local_data.size());
     } else {
-        raw_row->lease_type_ = Lease::TYPE_NA; 
+        statement = cass_prepared_bind(tagged_statement.prepared_statement_);
+        if (!statement) {
+            isc_throw(DbOperationError,
+                      "CqlLeaseStatsQuery::executeSelect(): unable to bind statement "
+                          << tagged_statement.name_);
+        }
+    }
+
+    // Set specific level of consistency if we're told to do so.
+    if (connection.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, connection.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(DbOperationError,
+                      "CqlLeaseStatsQuery::executeSelect(): unable to set statement "
+                      "consistency for statement "
+                          << tagged_statement.name_
+                          << ", Cassandra error code: " << cass_error_desc(rc));
+        }
+    }
+
+    CqlCommon::bindData(local_data, statement);
+
+    // Everything's ready. Call the actual statement.
+    future = cass_session_execute(connection.session_, statement);
+    if (!future) {
+        cass_statement_free(statement);
+        isc_throw(DbOperationError,
+                  "CqlLeaseStatsQuery::executeSelect(): no CassFuture for statement "
+                      << tagged_statement.name_);
+    }
+
+    // Wait for the statement execution to complete.
+    cass_future_wait(future);
+    const std::string error = connection.checkFutureError(
+        "CqlLeaseStatsQuery::executeSelect(): cass_session_execute() != CASS_OK",
+        future, statement_tag);
+    rc = cass_future_error_code(future);
+    if (rc != CASS_OK) {
+        cass_future_free(future);
+        cass_statement_free(statement);
+        isc_throw(DbOperationError, error);
+    }
+
+    // Get column values.
+    const CassResult* result_collection = cass_future_get_result(future);
+
+    // lease type is always NA for v4
+    if (!fetch_type_)  {
+        lease_type_ = Lease::TYPE_NA;
+    }
+
+    // Since we're currently forced to pull data for all leases, we
+    // iterate over them, aggregating them into cummulative LeaseStatsRows
+    AnyArray return_values;
+    CassIterator* rows = cass_iterator_from_result(result_collection);
+    while (cass_iterator_next(rows)) {
+        const CassRow* row = cass_iterator_get_row(rows);
+        createBindForSelect(return_values, statement_tag);
+        CqlCommon::getData(row, return_values);
+
+        LeaseStatsRow raw_row(subnet_id_, static_cast<Lease::Type>(lease_type_),
+                              lease_state_, 1);
+
+        auto cum_row = cummulative_rows_.find(raw_row);
+        if (cum_row != cummulative_rows_.end()) {
+            cummulative_rows_[raw_row] = cum_row->second + 1;
+        } else {
+            cummulative_rows_.insert(std::make_pair(raw_row, 1));
+        }
     }
 
-    raw_row->lease_state_ = state_;
-    return raw_row;
+    // Free resources.
+    cass_iterator_free(rows);
+    cass_result_free(result_collection);
+    cass_future_free(future);
+    cass_statement_free(statement);
+    return;
 }
 
 CqlLeaseMgr::CqlLeaseMgr(const DatabaseConnection::ParameterMap &parameters)
index 7ced8f2d7803d3eacbfa3aaa826ca1eb3661ca13..7911be567b12957f305c9a94b637fe3c28cd58fa 100644 (file)
@@ -101,6 +101,7 @@ struct LeaseStatsRow {
           lease_state_(lease_state), state_count_(state_count) {
     }
 
+    /// @brief Less-than operator
     bool operator< (const LeaseStatsRow &rhs) const {
         if (subnet_id_ < rhs.subnet_id_) {
             return (true);
index 12685c6c3a86f550606a356debb23d8570d66601..25fe6cfe0592bca1c1424e0b084ce91de38dbb8c 100644 (file)
@@ -724,4 +724,20 @@ TEST_F(CqlLeaseMgrTest, DISABLED_wipeLeases6) {
     testWipeLeases6();
 }
 
+struct Tom {
+    Tom() : id_(0)  {
+        std::cout << "new:" << this << std::endl;
+    }
+
+    ~Tom() {
+        std::cout << "del:" << this << std::endl;
+    }
+
+    bool operator< (const Tom &rhs) const {
+        return (rhs.id_ < this->id_);
+    }
+
+    int id_;
+};
+
 }  // namespace