From: Marcin Siodelski Date: Tue, 15 Jan 2019 16:53:41 +0000 (+0100) Subject: [#395,!201] Implemented AuditEntry class. X-Git-Tag: 100-implement-test-config-backend-dhcp6_base~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ac22858972f072bf0b1279605dc5f3149e3fd06d;p=thirdparty%2Fkea.git [#395,!201] Implemented AuditEntry class. --- diff --git a/src/lib/database/Makefile.am b/src/lib/database/Makefile.am index feb38acd8d..b7051cbf7f 100644 --- a/src/lib/database/Makefile.am +++ b/src/lib/database/Makefile.am @@ -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 index 0000000000..180d9dd051 --- /dev/null +++ b/src/lib/database/audit_entry.cc @@ -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 +#include + +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 index 0000000000..aa651996ce --- /dev/null +++ b/src/lib/database/audit_entry.h @@ -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 +#include +#include +#include + +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 AuditEntryPtr; + +} // end of namespace isc::db +} // end of namespace isc + +#endif diff --git a/src/lib/database/tests/Makefile.am b/src/lib/database/tests/Makefile.am index 4e1609d18f..cdf149b629 100644 --- a/src/lib/database/tests/Makefile.am +++ b/src/lib/database/tests/Makefile.am @@ -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 index 0000000000..4817ff3b26 --- /dev/null +++ b/src/lib/database/tests/audit_entry_unittest.cc @@ -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 +#include +#include +#include +#include +#include + +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(AuditEntry::ModificationType::CREATE)); + EXPECT_EQ(1, static_cast(AuditEntry::ModificationType::UPDATE)); + EXPECT_EQ(2, static_cast(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 + ("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 + ("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); + } +} + +}