]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#395,!201] Implemented AuditEntry class.
authorMarcin Siodelski <marcin@isc.org>
Tue, 15 Jan 2019 16:53:41 +0000 (17:53 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 15 Jan 2019 16:53:41 +0000 (17:53 +0100)
src/lib/database/Makefile.am
src/lib/database/audit_entry.cc [new file with mode: 0644]
src/lib/database/audit_entry.h [new file with mode: 0644]
src/lib/database/tests/Makefile.am
src/lib/database/tests/audit_entry_unittest.cc [new file with mode: 0644]

index feb38acd8d6239b7c2c304c3c9edc74ae85859c2..b7051cbf7f98a6f661992c671da0715fa078a9a8 100644 (file)
@@ -23,7 +23,8 @@ EXTRA_DIST = db_messages.mes
 CLEANFILES = *.gcno *.gcda db_messages.h db_messages.cc s-messages
 
 lib_LTLIBRARIES = libkea-database.la
-libkea_database_la_SOURCES  = backend_selector.cc backend_selector.h
+libkea_database_la_SOURCES  = audit_entry.cc audit_entry.h
+libkea_database_la_SOURCES += backend_selector.cc backend_selector.h
 libkea_database_la_SOURCES += database_connection.cc database_connection.h
 libkea_database_la_SOURCES += dbaccess_parser.h dbaccess_parser.cc
 libkea_database_la_SOURCES += db_exceptions.h
@@ -45,6 +46,7 @@ libkea_database_la_LDFLAGS = -no-undefined -version-info 1:0:0
 # Specify the headers for copying into the installation directory tree.
 libkea_database_includedir = $(pkgincludedir)/database
 libkea_database_include_HEADERS = \
+       audit_entry.h \
        backend_selector.h \
        database_connection.h \
        dbaccess_parser.h \
diff --git a/src/lib/database/audit_entry.cc b/src/lib/database/audit_entry.cc
new file mode 100644 (file)
index 0000000..180d9dd
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (C) 2019 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <database/audit_entry.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace db {
+
+AuditEntry::AuditEntry(const std::string& object_type,
+                       const uint64_t object_id,
+                       const ModificationType& modification_type,
+                       const boost::posix_time::ptime& modification_time,
+                       const std::string& log_message)
+    : object_type_(object_type),
+      object_id_(object_id),
+      modification_type_(modification_type),
+      modification_time_(modification_time),
+      log_message_(log_message) {
+    // Check if the provided values are sane.
+    validate();
+}
+
+AuditEntry::AuditEntry(const std::string& object_type,
+                       const uint64_t object_id,
+                       const ModificationType& modification_type,
+                       const std::string& log_message)
+    : object_type_(object_type),
+      object_id_(object_id),
+      modification_type_(modification_type),
+      modification_time_(boost::posix_time::microsec_clock::universal_time()),
+      log_message_(log_message) {
+    // Check if the provided values are sane.
+    validate();
+}
+
+void
+AuditEntry::validate() const {
+    // object type can't be empty
+    if (object_type_.empty()) {
+        isc_throw(BadValue, "object type can't be empty in the database "
+                  "audit entry");
+
+    // modification time must be a valid date time value
+    } else if (modification_time_.is_not_a_date_time()) {
+        isc_throw(BadValue, "modification time value is not a valid time "
+                  "object in the database audit entry");
+    }
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/audit_entry.h b/src/lib/database/audit_entry.h
new file mode 100644 (file)
index 0000000..aa65199
--- /dev/null
@@ -0,0 +1,157 @@
+// Copyright (C) 2019 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef AUDIT_ENTRY_H
+#define AUDIT_ENTRY_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace db {
+
+/// @brief Represents a single entry in the audit table.
+///
+/// The audit tables are used in the databases to track incremental
+/// changes, e.g. configuration changes applied via the configuration
+/// backend. The backend can query for the entries in the audit table
+/// to identify the SQL table in which the records were modified, the
+/// identifiers of the modified objects, type of the modifications,
+/// time of the modifications and optional log messages associated
+/// with the modifications.
+///
+/// The server should remember the most recent modification time out of
+/// all audit entries it has fetched. During the next attempt to fetch
+/// 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.
+///
+/// When the modification type of the entry is set to
+/// @c AuditEntry::ModificationType::DELETE, the corresponding
+/// configuration entry is already gone from the database. For example:
+/// when a subnet with ID of 123 is deleted from the dhcp4_subnet
+/// table, the audit entry similar to this will be stored in the audit
+/// table:
+///
+/// - object_type: "dhcp4_subnet"
+/// - object_id: 123
+/// - modification_type: 3 (DELETE)
+/// - modification_time: "2019-01-15 15:45:23"
+/// - log_message: "DHCPv4 subnet 123 deleted"
+///
+/// The subnet is instantly removed from the dhcp4_subnet table. When
+/// the server finds such entry in the audit table, it removes the
+/// subnet 123 from its (in-memory) configuration. There is no need
+/// make additional queries to fetch updated data from the dhcp4_subnet
+/// table, unless there are also audit entries indicating that some
+/// new subnets have been added or some subnets have been updated.
+class AuditEntry {
+public:
+
+    /// @brief Types of the modifications.
+    ///
+    /// The numbers representing those modification types correspond
+    /// to the values representing them in the database.
+    enum class ModificationType : uint8_t {
+        CREATE = 0,
+        UPDATE = 1,
+        DELETE = 2
+    };
+
+    /// @brief Constructor using explicit modification time.
+    ///
+    /// @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 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 std::string& log_message);
+
+    /// @brief Constructor using default modification time.
+    ///
+    /// @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 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 std::string& log_message);
+
+    /// @brief Returns object type.
+    ///
+    /// @return Name of the table in which the modification is present.
+    std::string getObjectType() const {
+        return (object_type_);
+    }
+
+    /// @brief Returns object id.
+    ///
+    /// @return Identifier of the added, updated or deleted object.
+    uint64_t getObjectId() const {
+        return (object_id_);
+    }
+
+    /// @brief Returns modification type.
+    ///
+    /// @return Type of the modification, e.g. DELETE.
+    ModificationType getModificationType() const {
+        return (modification_type_);
+    }
+
+    /// @brief Returns modification time.
+    ///
+    /// @return Modification time of the correponding record.
+    boost::posix_time::ptime getModificationTime() const {
+        return (modification_time_);
+    }
+
+    /// @brief Returns log message.
+    ///
+    /// @return Optional log message corresponding to the changes.
+    std::string getLogMessage() const {
+        return (log_message_);
+    }
+
+private:
+
+    /// @brief Validates the values specified for the audit entry.
+    ///
+    /// @throw BadValue if the object type is empty or if the
+    /// modification time is not a date time value.
+    void validate() const;
+
+    /// @brief Object type.
+    std::string object_type_;
+
+    /// @brief Object id.
+    uint64_t object_id_;
+
+    /// @brief Modification type.
+    ModificationType modification_type_;
+
+    /// @brief Modification time.
+    boost::posix_time::ptime modification_time_;
+
+    /// @brief Log message.
+    std::string log_message_;
+};
+
+/// @brief Pointer to the @c AuditEntry object.
+typedef boost::shared_ptr<AuditEntry> AuditEntryPtr;
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
index 4e1609d18f7342c6618da130e17399ab1894ed6b..cdf149b62937b96cb9f24b6846d5a73c1bf07d6e 100644 (file)
@@ -19,7 +19,8 @@ TESTS =
 if HAVE_GTEST
 TESTS += libdatabase_unittests
 
-libdatabase_unittests_SOURCES  = backend_selector_unittest.cc
+libdatabase_unittests_SOURCES  = audit_entry_unittest.cc
+libdatabase_unittests_SOURCES += backend_selector_unittest.cc
 libdatabase_unittests_SOURCES += database_connection_unittest.cc
 libdatabase_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdatabase_unittests_SOURCES += run_unittests.cc
diff --git a/src/lib/database/tests/audit_entry_unittest.cc b/src/lib/database/tests/audit_entry_unittest.cc
new file mode 100644 (file)
index 0000000..4817ff3
--- /dev/null
@@ -0,0 +1,134 @@
+// Copyright (C) 2019 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+#include <config.h>
+#include <database/audit_entry.h>
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::db;
+
+namespace {
+
+/// @brief Test fixture class for testing @c AuditEntry.
+class AuditEntryTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    AuditEntryTest()
+        : fixed_time_(now()) {
+    }
+
+    /// @brief Returns current time.
+    static boost::posix_time::ptime now() {
+        return (boost::posix_time::microsec_clock::universal_time());
+    }
+
+    /// @brief Returns always the same time value.
+    ///
+    /// The value is initialized when the test it started.
+    boost::posix_time::ptime fixedTime() const {
+        return (fixed_time_);
+    }
+
+    /// @brief Checks if the given time value is "close" to the
+    /// current time.
+    ///
+    /// This is useful in tests when the @c AuditEntry class sets the
+    /// modification time to a default value (in its constructor).
+    /// Because the test doesn't know the exact value to which the
+    /// modification time is set, it merely checks that this value
+    /// is earlier than current time and within the range of 1s.
+    ///
+    /// @param t time value to be checked.
+    bool almostEqualTime(const boost::posix_time::ptime& t) const {
+        auto current = now();
+
+        // The provided value must be a valid time.
+        if (t.is_not_a_date_time()) {
+            ADD_FAILURE() << "provided value is not a date time";
+            return (false);
+        }
+
+        // It must be earlier than current time.
+        if (t > current) {
+            ADD_FAILURE() << "provided time value is later than current time";
+            return (false);
+        }
+
+        // The difference must be lower than 1 second.
+        boost::posix_time::time_duration dur = current - t;
+        return (dur.total_milliseconds() < 1000);
+    }
+
+    /// @brief Fixed time value initialized in the constructor.
+    ///
+    /// This is used in tests that require the exact time values.
+    boost::posix_time::ptime fixed_time_;
+};
+
+// Checks that the modification time value can be cast to a number.
+TEST_F(AuditEntryTest, modificationType) {
+    EXPECT_EQ(0, static_cast<int>(AuditEntry::ModificationType::CREATE));
+    EXPECT_EQ(1, static_cast<int>(AuditEntry::ModificationType::UPDATE));
+    EXPECT_EQ(2, static_cast<int>(AuditEntry::ModificationType::DELETE));
+}
+
+// Checks that the audit entry can be created.
+TEST_F(AuditEntryTest, create) {
+
+    AuditEntryPtr audit_entry;
+
+    {
+        SCOPED_TRACE("create with modification time");
+
+        ASSERT_NO_THROW(audit_entry = boost::make_shared<AuditEntry>
+                        ("dhcp4_subnet", 10, AuditEntry::ModificationType::DELETE,
+                         fixedTime(), "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("deleted subnet 10", audit_entry->getLogMessage());
+    }
+
+    {
+        SCOPED_TRACE("create with default modification time");
+
+        ASSERT_NO_THROW(audit_entry = boost::make_shared<AuditEntry>
+                        ("dhcp4_option", 123, AuditEntry::ModificationType::CREATE,
+                         ""));
+        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_TRUE(audit_entry->getLogMessage().empty());
+    }
+}
+
+// Checks that invalid values for the audit entry are rejected.
+TEST_F(AuditEntryTest, createFailures) {
+    {
+        SCOPED_TRACE("empty object type");
+        EXPECT_THROW(AuditEntry("", 10, AuditEntry::ModificationType::DELETE,
+                                fixedTime(), "deleted subnet 10"),
+                     BadValue);
+    }
+
+    {
+        SCOPED_TRACE("not a date time");
+        EXPECT_THROW(AuditEntry("dhcp4_subnet", 10,
+                                AuditEntry::ModificationType::DELETE,
+                                boost::posix_time::ptime(), "deleted subnet 10"),
+                     BadValue);
+    }
+}
+
+}