]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1247] Checkpoint: fixed for audit entries
authorFrancis Dupont <fdupont@isc.org>
Wed, 17 Jun 2020 07:26:00 +0000 (09:26 +0200)
committerFrancis Dupont <fdupont@isc.org>
Mon, 6 Jul 2020 13:05:13 +0000 (15:05 +0200)
28 files changed:
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc
src/hooks/dhcp/mysql_cb/mysql_cb_messages.h
src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes
src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc
src/lib/database/audit_entry.cc
src/lib/database/audit_entry.h
src/lib/database/tests/audit_entry_unittest.cc
src/lib/dhcpsrv/config_backend_dhcp4.h
src/lib/dhcpsrv/config_backend_dhcp6.h
src/lib/dhcpsrv/config_backend_pool_dhcp4.cc
src/lib/dhcpsrv/config_backend_pool_dhcp4.h
src/lib/dhcpsrv/config_backend_pool_dhcp6.cc
src/lib/dhcpsrv/config_backend_pool_dhcp6.h
src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
src/lib/process/cb_ctl_base.h
src/lib/process/tests/cb_ctl_base_unittests.cc

index e519775e3f86f003ce91b5ea959fae90107ac742..436770195b0f9a5d420b8c73c4a323e81280444c 100644 (file)
@@ -3002,12 +3002,15 @@ MySqlConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector&
 
 AuditEntryCollection
 MySqlConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector& server_selector,
-        const boost::posix_time::ptime& modification_time) const {
+        const boost::posix_time::ptime& modification_time,
+        const uint64_t& modification_id) const {
     LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4)
-        .arg(util::ptimeToText(modification_time));
+      .arg(util::ptimeToText(modification_time))
+      .arg(modification_id);
     AuditEntryCollection audit_entries;
     impl_->getRecentAuditEntries(MySqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
-                                 server_selector, modification_time, audit_entries);
+                                 server_selector, modification_time,
+                                 modification_id, audit_entries);
     LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4_RESULT)
         .arg(audit_entries.size());
     return (audit_entries);
index 710a5d750316a92d9c365a7e7d5ff806e1c5ac84..f5fd30b40bddd704e919894bcaad6157be1647cb 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -230,10 +230,13 @@ public:
     /// @param selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers.
     ///
index 03764f9f981d147238f4170be4bccdc88d67e4c4..d66bf1072561e44ffb5359f03108ce506e0ac1fd 100644 (file)
@@ -3476,12 +3476,15 @@ MySqlConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector&
 
 AuditEntryCollection
 MySqlConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector& server_selector,
-        const boost::posix_time::ptime& modification_time) const {
+        const boost::posix_time::ptime& modification_time,
+        const uint64_t& modification_id) const {
     LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6)
-        .arg(util::ptimeToText(modification_time));
+      .arg(util::ptimeToText(modification_time))
+      .arg(modification_id);
     AuditEntryCollection audit_entries;
     impl_->getRecentAuditEntries(MySqlConfigBackendDHCPv6Impl::GET_AUDIT_ENTRIES6_TIME,
-                                 server_selector, modification_time, audit_entries);
+                                 server_selector, modification_time,
+                                 modification_id, audit_entries);
     LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6_RESULT)
         .arg(audit_entries.size());
     return (audit_entries);
index 1c97647f61a24a0382fc171344ad028580440e94..3f35e838c7956bfd831b5e11082bd74a23d58286 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -230,10 +230,13 @@ public:
     /// @param selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers.
     ///
index 39c43a9b38cbe7091d9dc03924cf50c50167766e..f3e6510942db40076f0739c0833af800c2a77cb1 100644 (file)
@@ -192,6 +192,7 @@ void
 MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
                                               const db::ServerSelector& server_selector,
                                               const boost::posix_time::ptime& modification_time,
+                                              const uint64_t& modification_id,
                                               AuditEntryCollection& audit_entries) {
     // Create the output bindings for receiving the data.
     MySqlBindingCollection out_bindings = {
@@ -200,6 +201,7 @@ MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
         MySqlBinding::createInteger<uint64_t>(), // object_id
         MySqlBinding::createInteger<uint8_t>(), // modification_type
         MySqlBinding::createTimestamp(), // modification_time
+        MySqlBinding::createInteger<uint64_t>(), // revision_id
         MySqlBinding::createString(AUDIT_ENTRY_LOG_MESSAGE_BUF_LENGTH) // log_message
     };
 
@@ -207,10 +209,12 @@ MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
 
     for (auto tag : tags) {
 
-        // There is only one input binding, modification time.
+        // There are only a few input bindings
         MySqlBindingCollection in_bindings = {
             MySqlBinding::createString(tag.get()),
-            MySqlBinding::createTimestamp(modification_time)
+            MySqlBinding::createTimestamp(modification_time),
+            MySqlBinding::createTimestamp(modification_time),
+            MySqlBinding::createInteger<uint64_t>(modification_id)
         };
 
         // Execute select.
@@ -227,7 +231,8 @@ MySqlConfigBackendImpl::getRecentAuditEntries(const int index,
                                    out_bindings[2]->getInteger<uint64_t>(),
                                    mod_type,
                                    out_bindings[4]->getTimestamp(),
-                                   out_bindings[5]->getStringOrDefault(""));
+                                   out_bindings[5]->getInteger<uint64_t>(),
+                                   out_bindings[6]->getStringOrDefault(""));
             audit_entries.insert(audit_entry);
         });
     }
index d2e3c6f987ae7e06d21b1e442c720566c3bcbda3..9c3639d77caefb21279e0d52b1d701e8df46c3a8 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -239,11 +239,14 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @param [out] audit_entries Reference to the container where fetched audit
     /// entries will be inserted.
     void getRecentAuditEntries(const int index,
                                const db::ServerSelector& server_selector,
                                const boost::posix_time::ptime& modification_time,
+                               const uint64_t& modification_id,
                                db::AuditEntryCollection& audit_entries);
 
     /// @brief Sends query to delete rows from a table.
index 6b3b9e280aeec84f7e9802968d71f816a87f8f34..b7511942ae72760b0d88e32bb4927dfea0607634 100644 (file)
@@ -1,4 +1,4 @@
-// File created from ../../../../src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes on Tue Jun 25 2019 16:12
+// File created from ../../../../src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes on Wed Jun 17 2020 02:55
 
 #include <cstddef>
 #include <log/message_types.h>
@@ -317,9 +317,9 @@ const char* values[] = {
     "MYSQL_CB_GET_OPTION_DEF6", "retrieving option definition code: %1 space: %2",
     "MYSQL_CB_GET_PORT4", "get port",
     "MYSQL_CB_GET_PORT6", "get port",
-    "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4", "retrieving audit entries from: %1",
+    "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4", "retrieving audit entries from: %1 %2",
     "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4_RESULT", "retrieving: %1 elements",
-    "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6", "retrieving audit entries from: %1",
+    "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6", "retrieving audit entries from: %1 %2",
     "MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6_RESULT", "retrieving: %1 elements",
     "MYSQL_CB_GET_SERVER4", "retrieving DHCPv4 server: %1",
     "MYSQL_CB_GET_SERVER6", "retrieving DHCPv6 server: %1",
index f8a143f66e42f300e2ac2b1def4ece99b9903472..723cc2a98d85c894cebf38646cef623a03e5baec 100644 (file)
@@ -1,4 +1,4 @@
-// File created from ../../../../src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes on Tue Jun 25 2019 16:12
+// File created from ../../../../src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes on Wed Jun 17 2020 02:55
 
 #ifndef MYSQL_CB_MESSAGES_H
 #define MYSQL_CB_MESSAGES_H
index e76df2864a7ddadd76524671cf7f663d2355965c..95d504e259464026a4c6ea8b92471681bffc2ad8 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 
 $NAMESPACE isc::cb
 
@@ -429,14 +429,14 @@ Debug message issued when triggered an action to retrieve port
 % MYSQL_CB_GET_PORT6 get port
 Debug message issued when triggered an action to retrieve port
 
-% MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4 retrieving audit entries from: %1
-Debug message issued when triggered an action to retrieve audit entries from specified time
+% MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4 retrieving audit entries from: %1 %2
+Debug message issued when triggered an action to retrieve audit entries from specified time and id.
 
 % MYSQL_CB_GET_RECENT_AUDIT_ENTRIES4_RESULT retrieving: %1 elements
 Debug message indicating the result of an action to retrieve audit entries from specified time
 
-% MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6 retrieving audit entries from: %1
-Debug message issued when triggered an action to retrieve audit entries from specified time
+% MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6 retrieving audit entries from: %1 %2
+Debug message issued when triggered an action to retrieve audit entries from specified time and id
 
 % MYSQL_CB_GET_RECENT_AUDIT_ENTRIES6_RESULT retrieving: %1 elements
 Debug message indicating the result of an action to retrieve audit entries from specified time
index 33b72352ee3e0960664425590902664ba84afd80..bf56ca3eb695aab3ea14a74064c9f00902355993 100644 (file)
@@ -617,13 +617,16 @@ namespace {
     "  a.object_id," \
     "  a.modification_type," \
     "  r.modification_ts," \
+    "  r.id, " \
     "  r.log_message " \
     "FROM " #table_prefix "_audit AS a " \
     "INNER JOIN " #table_prefix "_audit_revision AS r " \
     "  ON a.revision_id = r.id " \
     "INNER JOIN " #table_prefix "_server AS s" \
     "  ON r.server_id = s.id " \
-    "WHERE (s.tag = ? OR s.id = 1) AND (r.modification_ts > ?) " \
+    "WHERE (s.tag = ? OR s.id = 1) AND" \
+    " ((r.modification_ts > ?) OR " \
+    "  ((r.modification_ts = ?) AND (r.id > ?))) " \
     "ORDER BY r.modification_ts, r.id"
 #endif
 
index 9b7c29f56368e382fb22a8b2d92c274770ac0cd0..0a09237b9a52ef4195069ca1046900d43d1ad986 100644 (file)
@@ -432,7 +432,7 @@ public:
     std::string logExistingAuditEntries(const std::string& server_tag) {
         std::ostringstream s;
 
-        auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeTag>();
+        auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>();
 
         for (auto audit_entry_it = mod_time_idx.begin();
              audit_entry_it != mod_time_idx.end();
@@ -442,6 +442,7 @@ public:
               << audit_entry->getObjectId() << ", "
               << static_cast<int>(audit_entry->getModificationType()) << ", "
               << audit_entry->getModificationTime() << ", "
+              << audit_entry->getEntryId() << ", "
               << audit_entry->getLogMessage()
               << std::endl;
         }
@@ -492,11 +493,11 @@ public:
         // Audit entries for different server tags are stored in separate
         // containers.
         audit_entries_[tag] = cbptr_->getRecentAuditEntries(server_selector,
-                                                            timestamps_["two days ago"]);
+                                                            timestamps_["two days ago"], 0);
         ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
             << logExistingAuditEntries(tag);
 
-        auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeTag>();
+        auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
 
         // Iterate over specified number of entries starting from the most recent
         // one and check they have correct values.
index 6a5cf028156d5f993909254f01f2f6d8ab41d9dc..9a7a0e8ae3c5edc79726354d8a86597728c06157 100644 (file)
@@ -480,7 +480,7 @@ public:
     std::string logExistingAuditEntries(const std::string& server_tag) {
         std::ostringstream s;
 
-        auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeTag>();
+        auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>();
 
         for (auto audit_entry_it = mod_time_idx.begin();
              audit_entry_it != mod_time_idx.end();
@@ -490,6 +490,7 @@ public:
               << audit_entry->getObjectId() << ", "
               << static_cast<int>(audit_entry->getModificationType()) << ", "
               << audit_entry->getModificationTime() << ", "
+              << audit_entry->getEntryId() << ", "
               << audit_entry->getLogMessage()
               << std::endl;
         }
@@ -539,11 +540,11 @@ public:
         // Audit entries for different server tags are stored in separate
         // containers.
         audit_entries_[tag] = cbptr_->getRecentAuditEntries(server_selector,
-                                                            timestamps_["two days ago"]);
+                                                            timestamps_["two days ago"], 0);
         ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
             << logExistingAuditEntries(tag);
 
-        auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeTag>();
+        auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
 
         // Iterate over specified number of entries starting from the most recent
         // one and check they have correct values.
index b44c15ab6216679ace431b6540e7fdde501768a0..08d5de13e1a6cf330ce6388f657e98c0ee615b2d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,11 +15,13 @@ AuditEntry::AuditEntry(const std::string& object_type,
                        const uint64_t object_id,
                        const ModificationType& modification_type,
                        const boost::posix_time::ptime& modification_time,
+                       const uint64_t id,
                        const std::string& log_message)
     : object_type_(object_type),
       object_id_(object_id),
       modification_type_(modification_type),
       modification_time_(modification_time),
+      id_(id),
       log_message_(log_message) {
     // Check if the provided values are sane.
     validate();
@@ -28,11 +30,13 @@ AuditEntry::AuditEntry(const std::string& object_type,
 AuditEntry::AuditEntry(const std::string& object_type,
                        const uint64_t object_id,
                        const ModificationType& modification_type,
+                       const uint64_t id,
                        const std::string& log_message)
     : object_type_(object_type),
       object_id_(object_id),
       modification_type_(modification_type),
       modification_time_(boost::posix_time::microsec_clock::local_time()),
+      id_(id),
       log_message_(log_message) {
     // Check if the provided values are sane.
     validate();
@@ -43,10 +47,11 @@ AuditEntry::create(const std::string& object_type,
                    const uint64_t object_id,
                    const ModificationType& modification_type,
                    const boost::posix_time::ptime& modification_time,
+                   const uint64_t id,
                    const std::string& log_message) {
     return (boost::make_shared<AuditEntry>(object_type, object_id,
                                            modification_type,
-                                           modification_time,
+                                           modification_time, id,
                                            log_message));
 }
 
@@ -54,9 +59,10 @@ AuditEntryPtr
 AuditEntry::create(const std::string& object_type,
                    const uint64_t object_id,
                    const ModificationType& modification_type,
+                   const uint64_t id,
                    const std::string& log_message) {
     return (boost::make_shared<AuditEntry>(object_type, object_id,
-                                           modification_type,
+                                           modification_type, id,
                                            log_message));
 }
 
index 465705fcf6ba3ab3f2814c9e7a879b41cd24d472..f115fb1982161464be172643ef8f02ae20c13c5a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -39,6 +39,8 @@ typedef boost::shared_ptr<AuditEntry> AuditEntryPtr;
 /// the most recent modifications in the database it will query for all
 /// entries with later modification time than stored. That way the
 /// server queries only for the audit entries it hasn't fetched yet.
+/// In the case two (or more) successive audit entries have the same
+/// modification time the strictly increasing id is used.
 ///
 /// When the modification type of the entry is set to
 /// @c AuditEntry::ModificationType::DELETE, the corresponding
@@ -51,6 +53,7 @@ typedef boost::shared_ptr<AuditEntry> AuditEntryPtr;
 /// - object_id: 123
 /// - modification_type: 3 (DELETE)
 /// - modification_time: "2019-01-15 15:45:23"
+/// - id: 234
 /// - log_message: "DHCPv4 subnet 123 deleted"
 ///
 /// The subnet is instantly removed from the dhcp4_subnet table. When
@@ -72,18 +75,20 @@ public:
         DELETE = 2
     };
 
-    /// @brief Constructor using explicit modification time.
+    /// @brief Constructor using explicit modification time and id.
     ///
     /// @param object_type name of the table where data was modified.
     /// @param object_id identifier of the modified record in this table.
     /// @param modification_type type of the modification, e.g. DELETE.
     /// @param modification_time time of modification for that record.
+    /// @param id identifier of the entry itself.
     /// @param log_message optional log message associated with the
     /// modification.
     AuditEntry(const std::string& object_type,
                const uint64_t object_id,
                const ModificationType& modification_type,
                const boost::posix_time::ptime& modification_time,
+               const uint64_t id,
                const std::string& log_message);
 
     /// @brief Constructor using default modification time.
@@ -91,11 +96,13 @@ public:
     /// @param object_type name of the table where data was modified.
     /// @param object_id identifier of the modified record in this table.
     /// @param modification_type type of the modification, e.g. DELETE.
+    /// @param id identifier of the entry itself.
     /// @param log_message optional log message associated with the
     /// modification.
     AuditEntry(const std::string& object_type,
                const uint64_t object_id,
                const ModificationType& modification_type,
+               const uint64_t id,
                const std::string& log_message);
 
     /// @brief Factory function creating an instance of @c AuditEntry.
@@ -109,6 +116,7 @@ public:
     /// @param object_id identifier of the modified record in this table.
     /// @param modification_type type of the modification, e.g. DELETE.
     /// @param modification_time time of modification for that record.
+    /// @param id identifier of the entry itself.
     /// @param log_message optional log message associated with the
     /// modification.
     ///
@@ -117,6 +125,7 @@ public:
                                 const uint64_t object_id,
                                 const ModificationType& modification_type,
                                 const boost::posix_time::ptime& modification_time,
+                                const uint64_t id,
                                 const std::string& log_message);
 
     /// @brief Factory function creating an instance of @c AuditEntry.
@@ -129,6 +138,7 @@ public:
     /// @param object_type name of the table where data was modified.
     /// @param object_id identifier of the modified record in this table.
     /// @param modification_type type of the modification, e.g. DELETE.
+    /// @param id identifier of the entry itself.
     /// @param log_message optional log message associated with the
     /// modification.
     ///
@@ -136,6 +146,7 @@ public:
     static AuditEntryPtr create(const std::string& object_type,
                                 const uint64_t object_id,
                                 const ModificationType& modification_type,
+                                const uint64_t id,
                                 const std::string& log_message);
 
     /// @brief Returns object type.
@@ -166,6 +177,13 @@ public:
         return (modification_time_);
     }
 
+    /// @brief Returns entry id.
+    ///
+    /// @return Identifier of the entry.
+    uint64_t getEntryId() const {
+        return (id_);
+    }
+
     /// @brief Returns log message.
     ///
     /// @return Optional log message corresponding to the changes.
@@ -193,6 +211,9 @@ private:
     /// @brief Modification time.
     boost::posix_time::ptime modification_time_;
 
+    /// @brief Entry id.
+    uint64_t id_;
+
     /// @brief Log message.
     std::string log_message_;
 };
@@ -201,7 +222,7 @@ private:
 struct AuditEntryObjectTypeTag { };
 
 /// @brief Tag used to access index by modification time.
-struct AuditEntryModificationTimeTag { };
+struct AuditEntryModificationTimeIdTag { };
 
 /// @brief Multi index container holding @c AuditEntry instances.
 ///
@@ -229,13 +250,21 @@ typedef boost::multi_index_container<
             >
         >,
 
-        // Second index allows for accessing by the modification time.
+        // Second index allows for accessing by the modification time and id.
         boost::multi_index::ordered_non_unique<
-            boost::multi_index::tag<AuditEntryModificationTimeTag>,
-            boost::multi_index::const_mem_fun<
+            boost::multi_index::tag<AuditEntryModificationTimeIdTag>,
+            boost::multi_index::composite_key<
                 AuditEntry,
-                boost::posix_time::ptime,
-                &AuditEntry::getModificationTime
+                boost::multi_index::const_mem_fun<
+                    AuditEntry,
+                    boost::posix_time::ptime,
+                    &AuditEntry::getModificationTime
+                >,
+                boost::multi_index::const_mem_fun<
+                    AuditEntry,
+                    uint64_t,
+                    &AuditEntry::getEntryId
+                >
             >
         >
     >
index a669949ab90e2ee3ce57c3fcff675ce99dae586e..fbfdc1d2ae6e9809a5617d34f818be18483cc1f2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -91,11 +91,12 @@ TEST_F(AuditEntryTest, create) {
 
         ASSERT_NO_THROW(audit_entry = AuditEntry::create
                         ("dhcp4_subnet", 10, AuditEntry::ModificationType::DELETE,
-                         fixedTime(), "deleted subnet 10"));
+                         fixedTime(), 123, "deleted subnet 10"));
         EXPECT_EQ("dhcp4_subnet", audit_entry->getObjectType());
         EXPECT_EQ(10, audit_entry->getObjectId());
         EXPECT_EQ(AuditEntry::ModificationType::DELETE, audit_entry->getModificationType());
         EXPECT_EQ(fixedTime(), audit_entry->getModificationTime());
+        EXPECT_EQ(123, audit_entry->getEntryId());
         EXPECT_EQ("deleted subnet 10", audit_entry->getLogMessage());
     }
 
@@ -104,11 +105,12 @@ TEST_F(AuditEntryTest, create) {
 
         ASSERT_NO_THROW(audit_entry = AuditEntry::create
                         ("dhcp4_option", 123, AuditEntry::ModificationType::CREATE,
-                         ""));
+                         234, ""));
         EXPECT_EQ("dhcp4_option", audit_entry->getObjectType());
         EXPECT_EQ(123, audit_entry->getObjectId());
         EXPECT_EQ(AuditEntry::ModificationType::CREATE, audit_entry->getModificationType());
         EXPECT_TRUE(almostEqualTime(audit_entry->getModificationTime()));
+        EXPECT_EQ(234, audit_entry->getEntryId());
         EXPECT_TRUE(audit_entry->getLogMessage().empty());
     }
 }
@@ -118,7 +120,7 @@ TEST_F(AuditEntryTest, createFailures) {
     {
         SCOPED_TRACE("empty object type");
         EXPECT_THROW(AuditEntry("", 10, AuditEntry::ModificationType::DELETE,
-                                fixedTime(), "deleted subnet 10"),
+                                fixedTime(), 123, "deleted subnet 10"),
                      BadValue);
     }
 
@@ -126,7 +128,8 @@ TEST_F(AuditEntryTest, createFailures) {
         SCOPED_TRACE("not a date time");
         EXPECT_THROW(AuditEntry("dhcp4_subnet", 10,
                                 AuditEntry::ModificationType::DELETE,
-                                boost::posix_time::ptime(), "deleted subnet 10"),
+                                boost::posix_time::ptime(), 123,
+                                "deleted subnet 10"),
                      BadValue);
     }
 }
@@ -171,17 +174,17 @@ public:
     /// the tests.
     void createTestAuditEntries() {
         create("dhcp4_subnet", 10, AuditEntry::ModificationType::CREATE,
-               diffTime(-5), "added subnet 10");
+               diffTime(-5), 100, "added subnet 10");
         create("dhcp4_shared_network", 1, AuditEntry::ModificationType::CREATE,
-               diffTime(-5), "added shared network 1");
+               diffTime(-5), 110, "added shared network 1");
         create("dhcp4_shared_network", 120, AuditEntry::ModificationType::UPDATE,
-               diffTime(-8), "updated shared network 120");
+               diffTime(-8), 90, "updated shared network 120");
         create("dhcp4_subnet", 120, AuditEntry::ModificationType::DELETE,
-               diffTime(8), "deleted subnet 120");
+               diffTime(8), 130, "deleted subnet 120");
         create("dhcp4_subnet", 1000, AuditEntry::ModificationType::CREATE,
-               diffTime(4), "created subnet 1000");
+               diffTime(4), 120, "created subnet 1000");
         create("dhcp4_option", 15, AuditEntry::ModificationType::UPDATE,
-               diffTime(16), "updated option 15");
+               diffTime(16), 140, "updated option 15");
     }
 
     /// @brief Checks if the returned results range contains an @c AuditEntry
@@ -237,7 +240,7 @@ TEST_F(AuditEntryCollectionTest, getByObjectType) {
 
 // Checks that entries can be found by modification time.
 TEST_F(AuditEntryCollectionTest, getByModificationTime) {
-    const auto& mod_time_idx = audit_entries_.get<AuditEntryModificationTimeTag>();
+    const auto& mod_time_idx = audit_entries_.get<AuditEntryModificationTimeIdTag>();
 
     // Search for objects later than fixed time - 10s.
     auto lb = mod_time_idx.lower_bound(diffTime(-10));
@@ -282,4 +285,18 @@ TEST_F(AuditEntryCollectionTest, getByModificationTime) {
     ASSERT_EQ(0, std::distance(lb, mod_time_idx.end()));
 }
 
+// Checks that entries can be found by modification time and id.
+TEST_F(AuditEntryCollectionTest, getByModificationTimeAndId) {
+    const auto& mod_time_idx = audit_entries_.get<AuditEntryModificationTimeIdTag>();
+
+    // Search for objects later than added added subnet 10.
+    auto mod = boost::make_tuple(diffTime(-5), 100 + 1);
+    auto lb = mod_time_idx.lower_bound(mod);
+    ASSERT_EQ(4, std::distance(lb, mod_time_idx.end()));
+    EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+    EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+    EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end()));
+    EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+}
+
 }
index c9582745876609c38fa10b4136efc58cd7615da1..4f8ee608e946139ff49003c90563e19160270dfd 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -301,10 +301,13 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const = 0;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const = 0;
 
     /// @brief Retrieves all servers.
     ///
index 5f8ec3d1e9a50d9424dcc96497ef4255f664f943..b11056e55e4ad51d510010ba67ca769667a8aaf1 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -302,10 +302,13 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const = 0;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const = 0;
 
     /// @brief Retrieves all servers.
     ///
index cf0945202029a6b2ad984fd4b98c0705fda72543..cc2fb2b3150d59d78df998691b35992aa497e448 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -204,11 +204,12 @@ AuditEntryCollection
 ConfigBackendPoolDHCPv4::
 getRecentAuditEntries(const db::BackendSelector& backend_selector,
                       const db::ServerSelector& server_selector,
-                      const boost::posix_time::ptime& modification_time) const {
+                      const boost::posix_time::ptime& modification_time,
+                      const uint64_t& modification_id) const {
     AuditEntryCollection audit_entries;
     getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
         (&ConfigBackendDHCPv4::getRecentAuditEntries, backend_selector,
-         server_selector, audit_entries, modification_time);
+         server_selector, audit_entries, modification_time, modification_id);
     return (audit_entries);
 }
 
index 56497be43d2570f8449062590028eac4be54a398..8e6ba8b725a6107f1dbb6100c9301ae266931c74 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -227,11 +227,14 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::BackendSelector& backend_selector,
                           const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers from the particular backend.
     ///
index d36afbd1c7a27345d0c68918875d1fcfcecc054b..0539bc8697cbadbdf23382135a5d41ae14ee7065 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -204,11 +204,12 @@ AuditEntryCollection
 ConfigBackendPoolDHCPv6::
 getRecentAuditEntries(const db::BackendSelector& backend_selector,
                       const db::ServerSelector& server_selector,
-                      const boost::posix_time::ptime& modification_time) const {
+                      const boost::posix_time::ptime& modification_time,
+                      const uint64_t& modification_id) const {
     AuditEntryCollection audit_entries;
     getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
         (&ConfigBackendDHCPv6::getRecentAuditEntries, backend_selector,
-         server_selector, audit_entries, modification_time);
+         server_selector, audit_entries, modification_time, modification_id);
     return (audit_entries);
 }
 
index c79bb44abd099fd2046abbb97b447bd29a1b1f19..2765acb9a29c91160dcf3984c277a332ae085b27 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -226,11 +226,14 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::BackendSelector& backend_selector,
                           const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers from the particular backend.
     ///
index fac44f8163ce7105878820e89519da4b6a2d13ee..bd94ce325cb980f0fd8bc40bf35ce86548d66071 100644 (file)
@@ -70,7 +70,7 @@ public:
     void addCreateAuditEntry(const std::string& object_type) {
         AuditEntryPtr entry(new AuditEntry(object_type, 1234,
                                            AuditEntry::ModificationType::CREATE,
-                                           "some log message"));
+                                           2345, "some log message"));
         audit_entries_.insert(entry);
     }
 
@@ -86,7 +86,7 @@ public:
                              const uint64_t object_id) {
         AuditEntryPtr entry(new AuditEntry(object_type, object_id,
                                            AuditEntry::ModificationType::DELETE,
-                                           "some log message"));
+                                           1234, "some log message"));
         audit_entries_.insert(entry);
     }
 
index edf2ea6f2f40813a1955ed90bdaf7b5d3e36768e..0cbe3dd38c1a61fd35297d7cd2fa4a456109370b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -511,7 +511,8 @@ TestConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector&
 
 AuditEntryCollection
 TestConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector&,
-                                               const boost::posix_time::ptime&) const {
+                                               const boost::posix_time::ptime&,
+                                               const uint64_t&) const {
     return (AuditEntryCollection());
 }
 
index 20cde9869f1ef791715110461e140b791ef7b87d..fcda52b148a043add95620b0f261d2bd00d4b6df 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -220,10 +220,13 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers.
     ///
index 69b10d56f5eb5ce6767baf7932f7e54800b89866..3cdf0013346573fdd3cc90aaa092876e8b1a751f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -510,7 +510,8 @@ TestConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector&
 
 AuditEntryCollection
 TestConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector&,
-                                               const boost::posix_time::ptime&) const {
+                                               const boost::posix_time::ptime&,
+                                               const uint64_t&) const {
     return (AuditEntryCollection());
 }
 
index e598df977a155122805f2686d695b7dc6f4c996f..9a1d25a10649e1985a81b99011441802ec0548dc 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -220,10 +220,13 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time Timestamp being a lower limit for the returned
     /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) entries have modification_time.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const;
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
 
     /// @brief Retrieves all servers.
     ///
index e068efa158a1a1e1d019509decc166758c5963b4..37daaaea9ae0e5fd0002bb34e982996e1fd4ccb2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -62,8 +62,8 @@ namespace process {
 /// - use the "config-control" specification to connect to the specified
 ///   databases via the configuration backends,
 /// - fetch the audit trail to detect configuration updates,
-/// - store the timestamp of the most recent audit entry fetched from the
-///   database, so as next time it can fetch only the later updates.
+/// - store the timestamp and id of the most recent audit entry fetched
+///   from the database, so as next time it can fetch only the later updates.
 ///
 /// The server specific part to be implemented in derived classes must
 /// correctly interpret the audit entries and make appropriate API calls
@@ -92,9 +92,11 @@ public:
 
     /// @brief Constructor.
     ///
-    /// Sets the time of the last fetched audit entry to Jan 1st, 1970.
+    /// Sets the time of the last fetched audit entry to Jan 1st, 1970,
+    /// with id 0.
     CBControlBase()
-        : last_audit_entry_time_(getInitialAuditEntryTime()) {
+        : last_audit_entry_time_(getInitialAuditEntryTime()),
+          last_audit_entry_id_(0) {
     }
 
     /// @brief Virtual destructor.
@@ -107,10 +109,11 @@ public:
     /// @brief Resets the state of this object.
     ///
     /// Disconnects the configuration backends resets the recorded last
-    /// audit entry time.
+    /// audit entry time and id.
     void reset() {
         databaseConfigDisconnect();
         last_audit_entry_time_ = getInitialAuditEntryTime();
+        last_audit_entry_id_ = 0;
     }
 
     /// @brief (Re)connects to the specified configuration backends.
@@ -200,13 +203,16 @@ public:
 
         // Save the timestamp indicating last audit entry time.
         auto lb_modification_time = last_audit_entry_time_;
+        // Save the identifier indicating last audit entry id.
+        auto lb_modification_id = last_audit_entry_id_;
 
         audit_entries = getMgr().getPool()->getRecentAuditEntries(backend_selector,
                                                                   server_selector,
-                                                                  lb_modification_time);
+                                                                  lb_modification_time,
+                                                                  lb_modification_id);
         // Store the last audit entry time. It should be set to the most recent
         // audit entry fetched. If returned audit is empty we don't update.
-        updateLastAuditEntryTime(audit_entries);
+        updateLastAuditEntryTimeId(audit_entries);
 
         // If this is full reconfiguration we don't need the audit entries anymore.
         // Let's remove them and proceed as if they don't exist.
@@ -223,11 +229,12 @@ public:
                 databaseConfigApply(backend_selector, server_selector,
                                     lb_modification_time, audit_entries);
             } catch (...) {
-                // Revert last audit entry time so as we can retry from the
-                // last successful attempt.
+                // Revert last audit entry time and id so as we can retry
+                // from the last successful attempt.
                 /// @todo Consider reverting to the initial value to reload
                 /// the entire configuration if the update failed.
                 last_audit_entry_time_ = lb_modification_time;
+                last_audit_entry_id_ = lb_modification_id;
                 throw;
             }
         }
@@ -326,7 +333,7 @@ protected:
     /// returns without updating the timestamp.
     ///
     /// @param audit_entries reference to the collection of the fetched audit entries.
-    void updateLastAuditEntryTime(const db::AuditEntryCollection& audit_entries) {
+    void updateLastAuditEntryTimeId(const db::AuditEntryCollection& audit_entries) {
         // Do nothing if there are no audit entries. It is the case if
         // there were no updates to the configuration.
         if (audit_entries.empty()) {
@@ -335,12 +342,21 @@ protected:
 
         // Get the audit entries sorted by modification time and pick the
         // latest entry.
-        const auto& index = audit_entries.get<db::AuditEntryModificationTimeTag>();
-        last_audit_entry_time_ = ((*index.rbegin())->getModificationTime());
+        const auto& index = audit_entries.get<db::AuditEntryModificationTimeIdTag>();
+        last_audit_entry_time_ = (*index.rbegin())->getModificationTime();
+        last_audit_entry_id_ = (*index.rbegin())->getEntryId();
     }
 
-    /// @brief Stores the most recent audit entry.
+    /// @brief Stores the most recent audit entry timestamp.
     boost::posix_time::ptime last_audit_entry_time_;
+
+    /// @brief Stores the most recent audit entry identifier.
+    ///
+    /// The identifier is used when two (or more) audit entries have
+    /// the same timestamp. It is not used by itself because timestamps
+    /// are more user friendly. Unfortunately old versions of MySQL do not
+    /// support millisecond timestamps.
+    uint64_t last_audit_entry_id_;
 };
 
 } // end of namespace isc::process
index eb27040e55edceb9ee194e206705eb37de9407a7..1d53fc9ce494060846f1c5c017775ca5f4de7c23 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -39,19 +39,23 @@ public:
     ///
     /// @param modification_time The lower bound time for which audit
     /// entries should be returned.
+    /// @param modification_id The lower bound id for which audit
+    /// entries should be returned.
     ///
     /// @return Collection of audit entries later than specified time.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const db::ServerSelector&,
-                          const boost::posix_time::ptime& modification_time) const {
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t modification_id) const {
         db::AuditEntryCollection filtered_entries;
 
         // Use the index which orders the audit entries by timestamps.
-        const auto& index = audit_entries_.get<AuditEntryModificationTimeTag>();
+        const auto& index = audit_entries_.get<AuditEntryModificationTimeIdTag>();
 
         // Locate the first audit entry after the last one having the
-        // specified modification time.
-        auto first_entry = index.upper_bound(modification_time);
+        // specified modification time and id.
+        auto modification = boost::make_tuple(modification_time, modification_id);
+        auto first_entry = index.upper_bound(modification);
 
         // If there are any entries found return them.
         if (first_entry != index.end()) {
@@ -66,15 +70,18 @@ public:
     /// @param object_type Object type to be stored in the audit entry.
     /// @param object_id Object id to be stored in the audit entry.
     /// @param modification_time Audit entry modification time to be set.
+    /// @param modification_id Audit entry modification id to be set.
     void addAuditEntry(const ServerSelector&,
                        const std::string& object_type,
                        const uint64_t object_id,
-                       const boost::posix_time::ptime& modification_time) {
+                       const boost::posix_time::ptime& modification_time,
+                       const uint64_t modification_id) {
         // Create new audit entry from the specified parameters.
         AuditEntryPtr audit_entry(new AuditEntry(object_type,
                                                  object_id,
                                                  AuditEntry::ModificationType::CREATE,
                                                  modification_time,
+                                                 modification_id,
                                                  "added audit entry"));
 
         // The audit entries are held in the static variable so as they
@@ -143,15 +150,17 @@ public:
     /// @param object_type Object type to be stored in the audit entry.
     /// @param object_id Object id to be stored in the audit entry.
     /// @param modification_time Audit entry modification time to be set.
+    /// @param modification_id Audit entry modification id to be set.
     void addAuditEntry(const BackendSelector& backend_selector,
                        const ServerSelector& server_selector,
                        const std::string& object_type,
                        const uint64_t object_id,
-                       const boost::posix_time::ptime& modification_time) {
+                       const boost::posix_time::ptime& modification_time,
+                       const uint64_t modification_id) {
         createUpdateDeleteProperty<void, const std::string&, uint64_t,
-                                   const boost::posix_time::ptime&>
+                                   const boost::posix_time::ptime&, uint64_t>
             (&CBControlBackend::addAuditEntry, backend_selector, server_selector,
-             object_type, object_id, modification_time);
+             object_type, object_id, modification_time, modification_id);
     }
 
     /// @brief Retrieves the audit entries later than specified time.
@@ -160,16 +169,20 @@ public:
     /// @param server_selector Server selector.
     /// @param modification_time The lower bound time for which audit
     /// entries should be returned.
+    /// @param modification_id The lower bound id for which audit
+    /// entries should be returned.
     ///
     /// @return Collection of audit entries later than specified time.
     virtual db::AuditEntryCollection
     getRecentAuditEntries(const BackendSelector& backend_selector,
                           const ServerSelector& server_selector,
-                          const boost::posix_time::ptime& modification_time) const {
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t modification_id) const {
         AuditEntryCollection audit_entries;
         getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
             (&CBControlBackend::getRecentAuditEntries, backend_selector,
-             server_selector, audit_entries, modification_time);
+             server_selector, audit_entries, modification_time,
+             modification_id);
         return (audit_entries);
     }
 };
@@ -286,6 +299,11 @@ public:
         return (last_audit_entry_time_);
     }
 
+    /// @brief Returns the recorded id of last audit entry.
+    uint64_t getLastAuditEntryId() const {
+        return (last_audit_entry_id_);
+    }
+
     /// @brief Overwrites the last audit entry time.
     ///
     /// @param last_audit_entry_time New time to be set.
@@ -293,6 +311,13 @@ public:
         last_audit_entry_time_ = last_audit_entry_time;
     }
 
+    /// @brief Overwrites the last audit entry id.
+    ///
+    /// @param last_audit_entry_id New id to be set.
+    void setLastAuditEntryId(const uint64_t& last_audit_entry_id) {
+        last_audit_entry_id_ = last_audit_entry_id;
+    }
+
     /// @brief Enables the @c databaseConfigApply function to throw.
     ///
     /// This is useful to test scenarios when configuration merge fails.
@@ -400,6 +425,7 @@ TEST_F(CBControlBaseTest, reset) {
     cb_ctl_.setLastAuditEntryTime(timestamps_["tomorrow"]);
     cb_ctl_.reset();
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 }
 
 // This test verifies that it is correctly determined whether the
@@ -418,7 +444,7 @@ TEST_F(CBControlBaseTest, fetchConfigElement) {
     // no reason to fetch the data from the database.
     AuditEntryPtr audit_entry(new AuditEntry("dhcp4_subnet", 1234 ,
                                              AuditEntry::ModificationType::DELETE,
-                                             "added audit entry"));
+                                             2345, "added audit entry"));
     audit_entries.insert(audit_entry);
     EXPECT_FALSE(cb_ctl_.fetchConfigElement(audit_entries, "my_object_type"));
 
@@ -426,14 +452,14 @@ TEST_F(CBControlBaseTest, fetchConfigElement) {
     // This time we should get 'true'.
     audit_entry.reset(new AuditEntry("my_object_type", 5678,
                                      AuditEntry::ModificationType::CREATE,
-                                     "added audit entry"));
+                                     6789, "added audit entry"));
     audit_entries.insert(audit_entry);
     EXPECT_TRUE(cb_ctl_.fetchConfigElement(audit_entries, "my_object_type"));
 
     // Also we should get 'true' for the UPDATE case.
     audit_entry.reset(new AuditEntry("another_object_type",
                                      5678, AuditEntry::ModificationType::UPDATE,
-                                     "added audit entry"));
+                                     6790, "added audit entry"));
     audit_entries.insert(audit_entry);
     EXPECT_TRUE(cb_ctl_.fetchConfigElement(audit_entries, "another_object_type"));
 }
@@ -462,13 +488,15 @@ TEST_F(CBControlBaseTest, fetchAll) {
                                               ServerSelector::ALL(),
                                               "sql_table_2",
                                               1234,
-                                              timestamps_["yesterday"]);
+                                              timestamps_["yesterday"],
+                                              2345);
 
     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                               ServerSelector::ALL(),
                                               "sql_table_1",
                                               3456,
-                                              timestamps_["today"]);
+                                              timestamps_["today"],
+                                              4567);
 
     // Disconnect from the database in order to check that the
     // databaseConfigFetch reconnects.
@@ -480,6 +508,7 @@ TEST_F(CBControlBaseTest, fetchAll) {
     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 
     // Connect to the database and fetch the configuration.
     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
@@ -495,6 +524,7 @@ TEST_F(CBControlBaseTest, fetchAll) {
     // audit entry, so as the server will only later fetch config
     // updates after this timestamp.
     EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(4567, cb_ctl_.getLastAuditEntryId());
 }
 
 // This test verifies that the configuration can be fetched for a
@@ -510,6 +540,7 @@ TEST_F(CBControlBaseTest, fetchFromServer) {
     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 
     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
 
@@ -540,7 +571,8 @@ TEST_F(CBControlBaseTest, fetchUpdates) {
                                               ServerSelector::ALL(),
                                               "sql_table_1",
                                               3456,
-                                              timestamps_["today"]);
+                                              timestamps_["today"],
+                                              4567);
 
     // Verify that various indicators are set to their initial values.
     ASSERT_EQ(0, cb_ctl_.getMergesNum());
@@ -548,6 +580,7 @@ TEST_F(CBControlBaseTest, fetchUpdates) {
     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 
     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
                                                 CBControl::FetchMode::FETCH_UPDATE));
@@ -560,6 +593,7 @@ TEST_F(CBControlBaseTest, fetchUpdates) {
     EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
     // The last audit entry time should be set to the latest audit entry.
     EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(4567, cb_ctl_.getLastAuditEntryId());
 }
 
 // Check that the databaseConfigApply function is not called when there
@@ -571,6 +605,7 @@ TEST_F(CBControlBaseTest, fetchNoUpdates) {
     // entry we are going to add. That means that there will be
     // no new audit entries to fetch.
     cb_ctl_.setLastAuditEntryTime(timestamps_["yesterday"]);
+    cb_ctl_.setLastAuditEntryId(4567);
 
     ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
 
@@ -578,7 +613,8 @@ TEST_F(CBControlBaseTest, fetchNoUpdates) {
                                               ServerSelector::ALL(),
                                               "sql_table_1",
                                               3456,
-                                              timestamps_["yesterday"]);
+                                              timestamps_["yesterday"],
+                                              4567);
 
     ASSERT_EQ(0, cb_ctl_.getMergesNum());
 
@@ -603,7 +639,8 @@ TEST_F(CBControlBaseTest, fetchFailure) {
                                               ServerSelector::ALL(),
                                               "sql_table_1",
                                               3456,
-                                              timestamps_["today"]);
+                                              timestamps_["today"],
+                                              4567);
 
     // Configure the CBControl to always throw simulating the failure
     // during configuration merge.
@@ -615,6 +652,7 @@ TEST_F(CBControlBaseTest, fetchFailure) {
     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 
     ASSERT_THROW(cb_ctl_.databaseConfigFetch(config_base, CBControl::FetchMode::FETCH_UPDATE),
                  isc::Unexpected);
@@ -628,7 +666,7 @@ TEST_F(CBControlBaseTest, fetchFailure) {
     // The last audit entry time should not be modified because there was a merge
     // error.
     EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
-
+    EXPECT_EQ(0, cb_ctl_.getLastAuditEntryId());
 }
 
 }