From af5abc5785535f4e059a35be60dbbd012c61facf Mon Sep 17 00:00:00 2001 From: Lei YU Date: Tue, 7 Mar 2017 17:49:17 +0800 Subject: Initial implementation of HostEpoch When host time is set, the diff between the BmcTime and the value is saved to persistent storage; When host time is retrieved, return the BmcTime plus the diff as host's time. Add the unit test cases for HostEpoch. Change-Id: Ia55b93bfcba4f226ceaed8491136ea7afda7bd77 Signed-off-by: Lei YU --- Makefile.am | 3 +- bmc_epoch.cpp | 4 +- bmc_epoch.hpp | 4 +- configure.ac | 4 ++ host_epoch.cpp | 74 +++++++++++++++++++++ host_epoch.hpp | 69 ++++++++++++++++++++ main.cpp | 2 + test/Makefile.am | 3 +- test/TestHostEpoch.cpp | 173 +++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 host_epoch.cpp create mode 100644 host_epoch.hpp create mode 100644 test/TestHostEpoch.cpp diff --git a/Makefile.am b/Makefile.am index 3eef41a..0af260f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,7 +6,8 @@ noinst_LTLIBRARIES = libtimemanager.la libtimemanager_la_SOURCES = \ epoch_base.cpp \ - bmc_epoch.cpp + bmc_epoch.cpp \ + host_epoch.cpp phosphor_timemanager_SOURCES = \ main.cpp diff --git a/bmc_epoch.cpp b/bmc_epoch.cpp index 5923e8e..ed482ad 100644 --- a/bmc_epoch.cpp +++ b/bmc_epoch.cpp @@ -44,6 +44,6 @@ uint64_t BmcEpoch::elapsed(uint64_t value) return value; } -} -} +} // namespace time +} // namespace phosphor diff --git a/bmc_epoch.hpp b/bmc_epoch.hpp index 56d78e9..8c841a7 100644 --- a/bmc_epoch.hpp +++ b/bmc_epoch.hpp @@ -35,5 +35,5 @@ class BmcEpoch : public EpochBase uint64_t elapsed(uint64_t value) override; }; -} -} +} // namespace time +} // namespace phosphor diff --git a/configure.ac b/configure.ac index 03f8347..7a2ee4e 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,10 @@ AC_ARG_VAR(OBJPATH_HOST, [The host epoch Dbus root]) AS_IF([test "x$OBJPATH_HOST" == "x"], [OBJPATH_HOST="/xyz/openbmc_project/time/host"]) AC_DEFINE_UNQUOTED([OBJPATH_HOST], ["$OBJPATH_HOST"], [The host epoch Dbus root]) +AC_ARG_VAR(HOST_OFFSET_FILE, [The file to save host time offset]) +AS_IF([test "x$HOST_OFFSET_FILE" == "x"], [HOST_OFFSET_FILE="/var/lib/obmc/saved_host_offset"]) +AC_DEFINE_UNQUOTED([HOST_OFFSET_FILE], ["$HOST_OFFSET_FILE"], [The file to save host time offset]) + AC_CONFIG_FILES([Makefile test/Makefile]) AC_OUTPUT diff --git a/host_epoch.cpp b/host_epoch.cpp new file mode 100644 index 0000000..f45581d --- /dev/null +++ b/host_epoch.cpp @@ -0,0 +1,74 @@ +#include "host_epoch.hpp" + +#include + +#include + +namespace phosphor +{ +namespace time +{ +using namespace sdbusplus::xyz::openbmc_project::Time; +using namespace phosphor::logging; + +HostEpoch::HostEpoch(sdbusplus::bus::bus& bus, + const char* objPath) + : EpochBase(bus, objPath), + offset(readData(offsetFile)) +{ + // Empty +} + +uint64_t HostEpoch::elapsed() const +{ + // It does not needs to check owner when getting time + return (getTime() + offset).count(); +} + +uint64_t HostEpoch::elapsed(uint64_t value) +{ + if (timeOwner == Owner::BMC) + { + log("Setting HostTime in BMC owner is not allowed"); + // TODO: throw NotAllowed exception + return 0; + } + + // TODO: implement the logic of setting host time + // based on timeOwner and timeMode + + auto time = std::chrono::microseconds(value); + offset = time - getTime(); + + // Store the offset to file + writeData(offsetFile, offset.count()); + + server::EpochTime::elapsed(value); + return value; +} + +template +T HostEpoch::readData(const char* fileName) +{ + T data{}; + std::ifstream fs(fileName); + if (fs.is_open()) + { + fs >> data; + } + return data; +} + +template +void HostEpoch::writeData(const char* fileName, T&& data) +{ + std::ofstream fs(fileName, std::ios::out); + if (fs.is_open()) + { + fs << std::forward(data); + } +} + +} // namespace time +} // namespace phosphor + diff --git a/host_epoch.hpp b/host_epoch.hpp new file mode 100644 index 0000000..1252ff9 --- /dev/null +++ b/host_epoch.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "config.h" +#include "epoch_base.hpp" + +#include + +namespace phosphor +{ +namespace time +{ + +/** @class HostEpoch + * @brief OpenBMC HOST EpochTime implementation. + * @details A concrete implementation for xyz.openbmc_project.Time.EpochTime + * DBus API for HOST's epoch time. + */ +class HostEpoch : public EpochBase +{ + public: + friend class TestHostEpoch; + HostEpoch(sdbusplus::bus::bus& bus, + const char* objPath); + + /** + * @brief Get value of Elapsed property + * + * @return The elapsed microseconds since UTC + **/ + uint64_t elapsed() const override; + + /** + * @brief Set value of Elapsed property + * + * @param[in] value - The microseconds since UTC to set + * + * @return The updated elapsed microseconds since UTC + **/ + uint64_t elapsed(uint64_t value) override; + + private: + /** @brief The diff between BMC and Host time */ + std::chrono::microseconds offset; + + /** @brief The file to store the offset in File System. + * Read back when starts + **/ + static constexpr auto offsetFile = HOST_OFFSET_FILE; + + /** @brief Read data with type T from file + * + * @param[in] fileName - The name of file to read from + * + * @return The data with type T + */ + template + static T readData(const char* fileName); + + /** @brief Write data with type T to file + * + * @param[in] fileName - The name of file to write to + * @param[in] data - The data with type T to write to file + */ + template + static void writeData(const char* fileName, T&& data); +}; + +} // namespace time +} // namespace phosphor diff --git a/main.cpp b/main.cpp index 05ac453..b3f4b57 100644 --- a/main.cpp +++ b/main.cpp @@ -2,11 +2,13 @@ #include "config.h" #include "bmc_epoch.hpp" +#include "host_epoch.hpp" int main() { auto bus = sdbusplus::bus::new_default(); phosphor::time::BmcEpoch bmc(bus, OBJPATH_BMC); + phosphor::time::HostEpoch host(bus,OBJPATH_HOST); bus.request_name(BUSNAME); diff --git a/test/Makefile.am b/test/Makefile.am index 26c4c15..bdd258e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -7,7 +7,8 @@ check_PROGRAMS += test test_SOURCES = \ TestEpochBase.cpp \ - TestBmcEpoch.cpp + TestBmcEpoch.cpp \ + TestHostEpoch.cpp test_LDADD = $(top_builddir)/libtimemanager.la diff --git a/test/TestHostEpoch.cpp b/test/TestHostEpoch.cpp new file mode 100644 index 0000000..dca8d72 --- /dev/null +++ b/test/TestHostEpoch.cpp @@ -0,0 +1,173 @@ +#include +#include + +#include "host_epoch.hpp" +#include "config.h" + +namespace phosphor +{ +namespace time +{ + +using namespace std::chrono; +using namespace std::chrono_literals; + +class TestHostEpoch : public testing::Test +{ + public: + using Mode = EpochBase::Mode; + using Owner = EpochBase::Owner; + + sdbusplus::bus::bus bus; + HostEpoch hostEpoch; + + static constexpr auto FILE_NOT_EXIST = "path/to/file-not-exist"; + static constexpr auto FILE_OFFSET = "saved_host_offset"; + static constexpr auto delta = 2s; + + TestHostEpoch() + : bus(sdbusplus::bus::new_default()), + hostEpoch(bus, OBJPATH_HOST) + { + // Make sure the file does not exist + std::remove(FILE_NOT_EXIST); + } + ~TestHostEpoch() + { + // Cleanup test file + std::remove(FILE_OFFSET); + } + + // Proxies for HostEpoch's private members and functions + Mode getTimeMode() + { + return hostEpoch.timeMode; + } + Owner getTimeOwner() + { + return hostEpoch.timeOwner; + } + template + T readData(const char* fileName) + { + return HostEpoch::readData(fileName); + } + template + void writeData(const char* fileName, T&& data) + { + HostEpoch::writeData(fileName, std::forward(data)); + } + microseconds getOffset() + { + return hostEpoch.offset; + } + void setTimeOwner(Owner owner) + { + hostEpoch.timeOwner = owner; + } +}; + +TEST_F(TestHostEpoch, empty) +{ + EXPECT_EQ(Mode::NTP, getTimeMode()); + EXPECT_EQ(Owner::BMC, getTimeOwner()); +} + +TEST_F(TestHostEpoch, readDataFileNotExist) +{ + // When file does not exist, the default offset shall be 0 + microseconds offset(0); + auto value = readData(FILE_NOT_EXIST); + EXPECT_EQ(0, value); +} + +TEST_F(TestHostEpoch, writeAndReadData) +{ + // Write offset to file + microseconds offsetToWrite(1234567); + writeData(FILE_OFFSET, offsetToWrite.count()); + + // Read it back + microseconds offsetToRead; + offsetToRead = microseconds( + readData(FILE_OFFSET)); + EXPECT_EQ(offsetToWrite, offsetToRead); +} + +TEST_F(TestHostEpoch, setElapsedNotAllowed) +{ + // By default offset shall be 0 + EXPECT_EQ(0, getOffset().count()); + + // Set time in BMC mode is not allowed, + // so verify offset is still 0 after set time + microseconds diff = 1min; + hostEpoch.elapsed(hostEpoch.elapsed() + diff.count()); + EXPECT_EQ(0, getOffset().count()); +} + +TEST_F(TestHostEpoch, setElapsedInFutureAndGet) +{ + // Set to HOST owner so that we can set elapsed + setTimeOwner(Owner::HOST); + + // Get current time, and set future +1min time + auto t1 = hostEpoch.elapsed(); + EXPECT_NE(0, t1); + microseconds diff = 1min; + auto t2 = t1 + diff.count(); + hostEpoch.elapsed(t2); + + // Verify that the offset shall be positive, + // and less or equal to diff, and shall be not too less. + auto offset = getOffset(); + EXPECT_GT(offset, microseconds(0)); + EXPECT_LE(offset, diff); + diff -= delta; + EXPECT_GE(offset, diff); + + // Now get time shall be around future +1min time + auto epochNow = duration_cast( + system_clock::now().time_since_epoch()).count(); + auto elapsedGot = hostEpoch.elapsed(); + EXPECT_LT(epochNow, elapsedGot); + auto epochDiff = elapsedGot - epochNow; + diff = 1min; + EXPECT_GT(epochDiff, (diff - delta).count()); + EXPECT_LT(epochDiff, (diff + delta).count()); +} + +TEST_F(TestHostEpoch, setElapsedInPastAndGet) +{ + // Set to HOST owner so that we can set elapsed + setTimeOwner(Owner::HOST); + + // Get current time, and set past -1min time + auto t1 = hostEpoch.elapsed(); + EXPECT_NE(0, t1); + microseconds diff = 1min; + auto t2 = t1 - diff.count(); + hostEpoch.elapsed(t2); + + // Verify that the offset shall be negative, and the absolute value + // shall be equal or greater than diff, and shall not be too greater + auto offset = getOffset(); + EXPECT_LT(offset, microseconds(0)); + offset = -offset; + EXPECT_GE(offset, diff); + diff += 10s; + EXPECT_LE(offset, diff); + + // Now get time shall be around past -1min time + auto epochNow = duration_cast( + system_clock::now().time_since_epoch()).count(); + auto elapsedGot = hostEpoch.elapsed(); + EXPECT_LT(elapsedGot, epochNow); + auto epochDiff = epochNow - elapsedGot; + diff = 1min; + EXPECT_GT(epochDiff, (diff - delta).count()); + EXPECT_LT(epochDiff, (diff + delta).count()); +} + +} +} -- cgit v1.2.1