Implement HostEpoch set time logic
1. When setting host epoch, follow below logic:
Mode | Owner | Set Host Time
----- | ----- | -------------
NTP | BMC | Not allowed
NTP | HOST | Not allowed
NTP | SPLIT | OK, and just save offset
NTP | BOTH | Not allowed
MANUAL| BMC | Not allowed
MANUAL| HOST | OK, and set time to BMC
MANUAL| SPLIT | OK, and just save offset
MANUAL| BOTH | OK, and set time to BMC
2. If owner is SPLIT and BMC time is changed, update the offset accordinly;
3. Use timerfd to get notified on BMC time change, and update host time
diff accordingly;
4. Add unit test cases.
Change-Id: I2d60a821f7da9b689c579ae7ab672cc37967322c
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/bmc_epoch.cpp b/bmc_epoch.cpp
index ed482ad..7bc6c22 100644
--- a/bmc_epoch.cpp
+++ b/bmc_epoch.cpp
@@ -1,6 +1,22 @@
#include "bmc_epoch.hpp"
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+
+// Neeed to do this since its not exported outside of the kernel.
+// Refer : https://gist.github.com/lethean/446cea944b7441228298
+#ifndef TFD_TIMER_CANCEL_ON_SET
+#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
+#endif
+
+// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
+#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
namespace phosphor
{
@@ -11,9 +27,61 @@
BmcEpoch::BmcEpoch(sdbusplus::bus::bus& bus,
const char* objPath)
- : EpochBase(bus, objPath)
+ : EpochBase(bus, objPath),
+ bus(bus)
{
- // Empty
+ initialize();
+}
+
+void BmcEpoch::initialize()
+{
+ using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
+ Error::InternalFailure;
+
+ // Subscribe time change event
+ // Choose the MAX time that is possible to avoid mis fires.
+ constexpr itimerspec maxTime = {
+ {0, 0}, // it_interval
+ {TIME_T_MAX, 0}, //it_value
+ };
+
+ timeFd = timerfd_create(CLOCK_REALTIME, 0);
+ if (timeFd == -1)
+ {
+ log<level::ERR>("Failed to create timerfd",
+ entry("ERRNO=%d", errno),
+ entry("ERR=%s", strerror(errno)));
+ elog<InternalFailure>();
+ }
+
+ auto r = timerfd_settime(timeFd,
+ TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+ &maxTime,
+ nullptr);
+ if (r != 0)
+ {
+ log<level::ERR>("Failed to set timerfd",
+ entry("ERRNO=%d", errno),
+ entry("ERR=%s", strerror(errno)));
+ elog<InternalFailure>();
+ }
+
+ sd_event_source* es;
+ r = sd_event_add_io(bus.get_event(), &es,
+ timeFd, EPOLLIN, onTimeChange, this);
+ if (r < 0)
+ {
+ log<level::ERR>("Failed to add event",
+ entry("ERRNO=%d", -r),
+ entry("ERR=%s", strerror(-r)));
+ elog<InternalFailure>();
+ }
+ timeChangeEventSource.reset(es);
+}
+
+BmcEpoch::~BmcEpoch()
+{
+ close(timeFd);
}
uint64_t BmcEpoch::elapsed() const
@@ -24,6 +92,18 @@
uint64_t BmcEpoch::elapsed(uint64_t value)
{
+ /*
+ Mode | Owner | Set BMC Time
+ ----- | ----- | -------------
+ NTP | BMC | Not allowed
+ NTP | HOST | Not allowed
+ NTP | SPLIT | Not allowed
+ NTP | BOTH | Not allowed
+ MANUAL| BMC | OK
+ MANUAL| HOST | Not allowed
+ MANUAL| SPLIT | OK
+ MANUAL| BOTH | OK
+ */
if (timeMode == Mode::NTP)
{
log<level::ERR>("Setting BmcTime with NTP mode is not allowed");
@@ -37,13 +117,46 @@
return 0;
}
- auto time = std::chrono::microseconds(value);
+ auto time = microseconds(value);
setTime(time);
+ notifyBmcTimeChange(time);
+
server::EpochTime::elapsed(value);
return value;
}
+void BmcEpoch::setBmcTimeChangeListener(BmcTimeChangeListener* listener)
+{
+ timeChangeListener = listener;
+}
+
+void BmcEpoch::notifyBmcTimeChange(const microseconds& time)
+{
+ // Notify listener if it exists
+ if (timeChangeListener)
+ {
+ timeChangeListener->onBmcTimeChanged(time);
+ }
+}
+
+int BmcEpoch::onTimeChange(sd_event_source* es, int fd,
+ uint32_t /* revents */, void* userdata)
+{
+ auto bmcEpoch = static_cast<BmcEpoch*>(userdata);
+
+ std::array<char, 64> time {};
+
+ // We are not interested in the data here.
+ // So read until there is no new data here in the FD
+ while (read(fd, time.data(), time.max_size()) > 0);
+
+ log<level::INFO>("BMC system time is changed");
+ bmcEpoch->notifyBmcTimeChange(bmcEpoch->getTime());
+
+ return 0;
+}
+
} // namespace time
} // namespace phosphor
diff --git a/bmc_epoch.hpp b/bmc_epoch.hpp
index 8c841a7..228a39d 100644
--- a/bmc_epoch.hpp
+++ b/bmc_epoch.hpp
@@ -1,12 +1,17 @@
#pragma once
+#include "bmc_time_change_listener.hpp"
#include "epoch_base.hpp"
+#include <chrono>
+
namespace phosphor
{
namespace time
{
+using namespace std::chrono;
+
/** @class BmcEpoch
* @brief OpenBMC BMC EpochTime implementation.
* @details A concrete implementation for xyz.openbmc_project.Time.EpochTime
@@ -18,6 +23,7 @@
friend class TestBmcEpoch;
BmcEpoch(sdbusplus::bus::bus& bus,
const char* objPath);
+ ~BmcEpoch();
/**
* @brief Get value of Elapsed property
@@ -33,6 +39,55 @@
* @return The updated elapsed microseconds since UTC
**/
uint64_t elapsed(uint64_t value) override;
+
+ /** @brief Set the listner for bmc time change
+ *
+ * @param[in] listener - The pointer to the listener
+ */
+ void setBmcTimeChangeListener(BmcTimeChangeListener* listener);
+
+ private:
+ /** @brief The fd for time change event */
+ int timeFd = -1;
+
+ /** @brief Initialize timerFd related resource */
+ void initialize();
+
+ /** @brief Notify the listeners that bmc time is changed
+ *
+ * @param[in] time - The epoch time in microseconds to notify
+ */
+ void notifyBmcTimeChange(const microseconds& time);
+
+ /** @brief The callback function on system time change
+ *
+ * @param[in] es - Source of the event
+ * @param[in] fd - File descriptor of the timer
+ * @param[in] revents - Not used
+ * @param[in] userdata - User data pointer
+ */
+ static int onTimeChange(sd_event_source* es, int fd,
+ uint32_t revents, void* userdata);
+
+ /** @brief The reference of sdbusplus bus */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief The deleter of sd_event_source */
+ std::function<void(sd_event_source*)> sdEventSourceDeleter =
+ [] (sd_event_source* p) {
+ if (p)
+ {
+ sd_event_source_unref(p);
+ }
+ };
+ using SdEventSource = std::unique_ptr<sd_event_source,
+ decltype(sdEventSourceDeleter)>;
+
+ /** @brief The event source on system time change */
+ SdEventSource timeChangeEventSource {nullptr, sdEventSourceDeleter};
+
+ /** @brief The listener for bmc time change */
+ BmcTimeChangeListener* timeChangeListener = nullptr;
};
} // namespace time
diff --git a/bmc_time_change_listener.hpp b/bmc_time_change_listener.hpp
new file mode 100644
index 0000000..44fc479
--- /dev/null
+++ b/bmc_time_change_listener.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace time
+{
+
+class BmcTimeChangeListener
+{
+ public:
+ virtual ~BmcTimeChangeListener() = default;
+
+ /** @brief Notified on bmc time is changed
+ *
+ * @param[in] bmcTime - The epoch time in microseconds
+ */
+ virtual void onBmcTimeChanged(
+ const std::chrono::microseconds& bmcTime) = 0;
+};
+
+}
+}
diff --git a/host_epoch.cpp b/host_epoch.cpp
index 3b4c00e..129d224 100644
--- a/host_epoch.cpp
+++ b/host_epoch.cpp
@@ -9,43 +9,107 @@
{
using namespace sdbusplus::xyz::openbmc_project::Time;
using namespace phosphor::logging;
+using namespace std::chrono;
HostEpoch::HostEpoch(sdbusplus::bus::bus& bus,
const char* objPath)
: EpochBase(bus, objPath),
offset(utils::readData<decltype(offset)::rep>(offsetFile))
{
- // Empty
+ // Initialize the diffToSteadyClock
+ auto steadyTime = duration_cast<microseconds>(
+ steady_clock::now().time_since_epoch());
+ diffToSteadyClock = getTime() + offset - steadyTime;
}
uint64_t HostEpoch::elapsed() const
{
- // It does not needs to check owner when getting time
- return (getTime() + offset).count();
+ auto ret = getTime();
+ if (timeOwner == Owner::SPLIT)
+ {
+ ret += offset;
+ }
+ return ret.count();
}
uint64_t HostEpoch::elapsed(uint64_t value)
{
- if (timeOwner == Owner::BMC)
+ /*
+ Mode | Owner | Set Host Time
+ ----- | ----- | -------------
+ NTP | BMC | Not allowed
+ NTP | HOST | Not allowed
+ NTP | SPLIT | OK, and just save offset
+ NTP | BOTH | Not allowed
+ MANUAL| BMC | Not allowed
+ MANUAL| HOST | OK, and set time to BMC
+ MANUAL| SPLIT | OK, and just save offset
+ MANUAL| BOTH | OK, and set time to BMC
+ */
+ if (timeOwner == Owner::BMC ||
+ (timeMode == Mode::NTP
+ && (timeOwner == Owner::HOST || timeOwner == Owner::BOTH)))
{
- log<level::ERR>("Setting HostTime in BMC owner is not allowed");
+ log<level::ERR>("Setting HostTime is not allowed");
// TODO: throw NotAllowed exception
return 0;
}
- // TODO: implement the logic of setting host time
- // based on timeOwner and timeMode
+ auto time = microseconds(value);
+ if (timeOwner == Owner::SPLIT)
+ {
+ // Calculate the offset between host and bmc time
+ offset = time - getTime();
+ saveOffset();
- auto time = std::chrono::microseconds(value);
- offset = time - getTime();
-
- // Store the offset to file
- utils::writeData(offsetFile, offset.count());
+ // Calculate the diff between host and steady time
+ auto steadyTime = duration_cast<microseconds>(
+ steady_clock::now().time_since_epoch());
+ diffToSteadyClock = time - steadyTime;
+ }
+ else
+ {
+ // Set time to BMC
+ setTime(time);
+ }
server::EpochTime::elapsed(value);
return value;
}
+void HostEpoch::onOwnerChanged(Owner owner)
+{
+ // If timeOwner is changed to SPLIT, the offset shall be preserved
+ // Otherwise it shall be cleared;
+ timeOwner = owner;
+ if (timeOwner != Owner::SPLIT)
+ {
+ offset = microseconds(0);
+ saveOffset();
+ }
+}
+
+void HostEpoch::saveOffset()
+{
+ // Store the offset to file
+ utils::writeData(offsetFile, offset.count());
+}
+
+void HostEpoch::onBmcTimeChanged(const microseconds& bmcTime)
+{
+ // If owner is split and BMC time is changed,
+ // the offset shall be adjusted
+ if (timeOwner == Owner::SPLIT)
+ {
+ auto steadyTime = duration_cast<microseconds>(
+ steady_clock::now().time_since_epoch());
+ auto hostTime = steadyTime + diffToSteadyClock;
+ offset = hostTime - bmcTime;
+
+ saveOffset();
+ }
+}
+
} // namespace time
} // namespace phosphor
diff --git a/host_epoch.hpp b/host_epoch.hpp
index 3798f8f..1eb3167 100644
--- a/host_epoch.hpp
+++ b/host_epoch.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "bmc_time_change_listener.hpp"
#include "config.h"
#include "epoch_base.hpp"
@@ -15,7 +16,7 @@
* @details A concrete implementation for xyz.openbmc_project.Time.EpochTime
* DBus API for HOST's epoch time.
*/
-class HostEpoch : public EpochBase
+class HostEpoch : public EpochBase, public BmcTimeChangeListener
{
public:
friend class TestHostEpoch;
@@ -38,10 +39,31 @@
**/
uint64_t elapsed(uint64_t value) override;
+ /** @brief Notified on time owner changed */
+ void onOwnerChanged(Owner owner) override;
+
+ /** @brief Notified on bmc time is changed
+ *
+ * @param[in] bmcTime - The epoch time in microseconds
+ */
+ void onBmcTimeChanged(
+ const std::chrono::microseconds& bmcTime) override;
+
private:
/** @brief The diff between BMC and Host time */
std::chrono::microseconds offset;
+ /**
+ * @brief The diff between host time and steady clock
+ * @details This diff is used to calculate the host time if BMC time
+ * is changed and the owner is SPLIT.
+ * Without this the host time is lost if BMC time is changed.
+ */
+ std::chrono::microseconds diffToSteadyClock;
+
+ /** @brief Save the offset value into offsetFile */
+ void saveOffset();
+
/** @brief The file to store the offset in File System.
* Read back when starts
**/
diff --git a/main.cpp b/main.cpp
index b8a5adb..1798dc0 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,6 +8,20 @@
int main()
{
auto bus = sdbusplus::bus::new_default();
+ sd_event* event = nullptr;
+
+ auto eventDeleter = [](sd_event* e) {
+ e = sd_event_unref(e);
+ };
+ using SdEvent = std::unique_ptr<sd_event, decltype(eventDeleter)>;
+
+ // acquire a referece to the default event loop
+ sd_event_default(&event);
+ SdEvent sdEvent {event, eventDeleter};
+ event = nullptr;
+
+ // attach bus to this event loop
+ bus.attach_event(sdEvent.get(), SD_EVENT_PRIORITY_NORMAL);
// Add sdbusplus ObjectManager
sdbusplus::server::manager::manager bmcEpochObjManager(bus, OBJPATH_BMC);
@@ -19,13 +33,14 @@
manager.addListener(&bmc);
manager.addListener(&host);
+ bmc.setBmcTimeChangeListener(&host);
bus.request_name(BUSNAME);
- while (true)
- {
- bus.process_discard();
- bus.wait();
- }
+ // Start event loop for all sd-bus events and timer event
+ sd_event_loop(bus.get_event());
+
+ bus.detach_event();
+
return 0;
}
diff --git a/test/TestBmcEpoch.cpp b/test/TestBmcEpoch.cpp
index 2fd55a5..4661e20 100644
--- a/test/TestBmcEpoch.cpp
+++ b/test/TestBmcEpoch.cpp
@@ -1,45 +1,67 @@
#include <sdbusplus/bus.hpp>
#include <gtest/gtest.h>
+#include <memory>
#include "bmc_epoch.hpp"
#include "config.h"
#include "types.hpp"
+#include "mocked_bmc_time_change_listener.hpp"
namespace phosphor
{
namespace time
{
+using ::testing::_;
using namespace std::chrono;
+
class TestBmcEpoch : public testing::Test
{
public:
sdbusplus::bus::bus bus;
- BmcEpoch bmcEpoch;
+ sd_event* event;
+ MockBmcTimeChangeListener listener;
+ std::unique_ptr<BmcEpoch> bmcEpoch;
TestBmcEpoch()
- : bus(sdbusplus::bus::new_default()),
- bmcEpoch(bus, OBJPATH_BMC)
+ : bus(sdbusplus::bus::new_default())
{
- // Empty
+ // BmcEpoch requires sd_event to init
+ sd_event_default(&event);
+ bus.attach_event(event, SD_EVENT_PRIORITY_NORMAL);
+ bmcEpoch = std::make_unique<BmcEpoch>(bus, OBJPATH_BMC);
+ bmcEpoch->setBmcTimeChangeListener(&listener);
+ }
+
+ ~TestBmcEpoch()
+ {
+ bus.detach_event();
+ sd_event_unref(event);
}
// Proxies for BmcEpoch's private members and functions
Mode getTimeMode()
{
- return bmcEpoch.timeMode;
+ return bmcEpoch->timeMode;
}
Owner getTimeOwner()
{
- return bmcEpoch.timeOwner;
+ return bmcEpoch->timeOwner;
}
void setTimeOwner(Owner owner)
{
- bmcEpoch.timeOwner = owner;
+ bmcEpoch->timeOwner = owner;
}
void setTimeMode(Mode mode)
{
- bmcEpoch.timeMode = mode;
+ bmcEpoch->timeMode = mode;
+ }
+ void triggerTimeChange()
+ {
+ bmcEpoch->onTimeChange(nullptr,
+ -1,
+ 0,
+ bmcEpoch.get());
}
};
@@ -51,9 +73,9 @@
TEST_F(TestBmcEpoch, getElapsed)
{
- auto t1 = bmcEpoch.elapsed();
+ auto t1 = bmcEpoch->elapsed();
EXPECT_NE(0, t1);
- auto t2 = bmcEpoch.elapsed();
+ auto t2 = bmcEpoch->elapsed();
EXPECT_GE(t2, t1);
}
@@ -62,13 +84,13 @@
auto epochNow = duration_cast<microseconds>(
system_clock::now().time_since_epoch()).count();
// In NTP mode, setting time is not allowed
- auto ret = bmcEpoch.elapsed(epochNow);
+ auto ret = bmcEpoch->elapsed(epochNow);
EXPECT_EQ(0, ret);
// In Host owner, setting time is not allowed
setTimeMode(Mode::MANUAL);
setTimeOwner(Owner::HOST);
- ret = bmcEpoch.elapsed(epochNow);
+ ret = bmcEpoch->elapsed(epochNow);
EXPECT_EQ(0, ret);
}
@@ -79,5 +101,12 @@
// But for now we can not test it
}
+TEST_F(TestBmcEpoch, onTimeChange)
+{
+ // On BMC time change, the listner is expected to be notified
+ EXPECT_CALL(listener, onBmcTimeChanged(_)).Times(1);
+ triggerTimeChange();
+}
+
}
}
diff --git a/test/TestHostEpoch.cpp b/test/TestHostEpoch.cpp
index 0ff8a11..13d604d 100644
--- a/test/TestHostEpoch.cpp
+++ b/test/TestHostEpoch.cpp
@@ -14,6 +14,8 @@
using namespace std::chrono;
using namespace std::chrono_literals;
+const constexpr microseconds USEC_ZERO{0};
+
class TestHostEpoch : public testing::Test
{
public:
@@ -22,7 +24,7 @@
static constexpr auto FILE_NOT_EXIST = "path/to/file-not-exist";
static constexpr auto FILE_OFFSET = "saved_host_offset";
- static constexpr auto delta = 2s;
+ const microseconds delta = 2s;
TestHostEpoch()
: bus(sdbusplus::bus::new_default()),
@@ -50,9 +52,86 @@
{
return hostEpoch.offset;
}
+ void setOffset(microseconds us)
+ {
+ hostEpoch.offset = us;
+ }
void setTimeOwner(Owner owner)
{
- hostEpoch.timeOwner = owner;
+ hostEpoch.onOwnerChanged(owner);
+ }
+ void setTimeMode(Mode mode)
+ {
+ hostEpoch.onModeChanged(mode);
+ }
+
+ void checkSettingTimeNotAllowed()
+ {
+ // By default offset shall be 0
+ EXPECT_EQ(0, getOffset().count());
+
+ // Set time 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());
+ // TODO: when gmock is ready, check there is no call to timedatectl
+ }
+
+ void checkSetSplitTimeInFuture()
+ {
+ // 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, USEC_ZERO);
+ EXPECT_LE(offset, diff);
+ diff -= delta;
+ EXPECT_GE(offset, diff);
+
+ // Now get time shall be around future +1min time
+ auto epochNow = duration_cast<microseconds>(
+ 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());
+ }
+ void checkSetSplitTimeInPast()
+ {
+ // 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, USEC_ZERO);
+ 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<microseconds>(
+ 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());
}
};
@@ -80,26 +159,90 @@
// Read it back
microseconds offsetToRead;
offsetToRead = microseconds(
- utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
+ utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
EXPECT_EQ(offsetToWrite, offsetToRead);
}
-TEST_F(TestHostEpoch, setElapsedNotAllowed)
+TEST_F(TestHostEpoch, setElapsedInNtpBmc)
{
- // 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());
+ // Set time in NTP/BMC is not allowed
+ setTimeMode(Mode::NTP);
+ setTimeOwner(Owner::BMC);
+ checkSettingTimeNotAllowed();
}
-TEST_F(TestHostEpoch, setElapsedInFutureAndGet)
+TEST_F(TestHostEpoch, setElapsedInNtpHost)
{
- // Set to HOST owner so that we can set elapsed
+ // Set time in NTP/HOST is not allowed
+ setTimeMode(Mode::NTP);
setTimeOwner(Owner::HOST);
+ checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpSplit)
+{
+ // Set time in NTP/SPLIT, offset will be set
+ setTimeMode(Mode::NTP);
+ setTimeOwner(Owner::SPLIT);
+
+ checkSetSplitTimeInFuture();
+
+ // Reset offset
+ setOffset(USEC_ZERO);
+ checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpBoth)
+{
+ // Set time in NTP/BOTH is not allowed
+ setTimeMode(Mode::NTP);
+ setTimeOwner(Owner::BOTH);
+ checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBmc)
+{
+ // Set time in MANUAL/BMC is not allowed
+ setTimeMode(Mode::MANUAL);
+ setTimeOwner(Owner::BMC);
+ checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualHost)
+{
+ // Set time in MANUAL/HOST, time will be set to BMC
+ // However it requies gmock to test this case
+ // TODO: when gmock is ready, test this case.
+ setTimeMode(Mode::MANUAL);
+ setTimeOwner(Owner::HOST);
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualSplit)
+{
+ // Set to SPLIT owner so that offset will be set
+ setTimeMode(Mode::MANUAL);
+ setTimeOwner(Owner::SPLIT);
+
+ checkSetSplitTimeInFuture();
+
+ // Reset offset
+ setOffset(USEC_ZERO);
+ checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBoth)
+{
+ // Set time in MANUAL/BOTH, time will be set to BMC
+ // However it requies gmock to test this case
+ // TODO: when gmock is ready, test this case.
+ setTimeMode(Mode::MANUAL);
+ setTimeOwner(Owner::BOTH);
+}
+
+TEST_F(TestHostEpoch, setElapsedInSplitAndBmcTimeIsChanged)
+{
+ // Set to SPLIT owner so that offset will be set
+ setTimeOwner(Owner::SPLIT);
// Get current time, and set future +1min time
auto t1 = hostEpoch.elapsed();
@@ -111,52 +254,38 @@
// 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_GT(offset, USEC_ZERO);
EXPECT_LE(offset, diff);
diff -= delta;
EXPECT_GE(offset, diff);
- // Now get time shall be around future +1min time
- auto epochNow = duration_cast<microseconds>(
- 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());
+ // Now BMC time is changed to future +1min
+ hostEpoch.onBmcTimeChanged(microseconds(t2));
+
+ // Verify that the offset shall be around zero since it's almost
+ // the same as BMC time
+ offset = getOffset();
+ if (offset.count() < 0)
+ {
+ offset = microseconds(-offset.count());
+ }
+ EXPECT_LE(offset, delta);
}
-TEST_F(TestHostEpoch, setElapsedInPastAndGet)
+TEST_F(TestHostEpoch, clearOffsetOnOwnerChange)
{
- // Set to HOST owner so that we can set elapsed
- setTimeOwner(Owner::HOST);
+ EXPECT_EQ(USEC_ZERO, getOffset());
- // 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);
+ setTimeOwner(Owner::SPLIT);
+ hostEpoch.onBmcTimeChanged(microseconds(hostEpoch.elapsed()) + 1min);
- // 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 offset shall be non zero
+ EXPECT_NE(USEC_ZERO, getOffset());
- // Now get time shall be around past -1min time
- auto epochNow = duration_cast<microseconds>(
- 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());
+ setTimeOwner(Owner::BOTH);
+
+ // Now owner is BOTH, the offset shall be cleared
+ EXPECT_EQ(USEC_ZERO, getOffset());
}
}
diff --git a/test/mocked_bmc_time_change_listener.hpp b/test/mocked_bmc_time_change_listener.hpp
new file mode 100644
index 0000000..9b2c81c
--- /dev/null
+++ b/test/mocked_bmc_time_change_listener.hpp
@@ -0,0 +1,16 @@
+#pragma once
+#include <gmock/gmock.h>
+#include "bmc_time_change_listener.hpp"
+
+namespace phosphor {
+namespace time {
+
+class MockBmcTimeChangeListener : public BmcTimeChangeListener {
+ public:
+ MOCK_METHOD1(onBmcTimeChanged,
+ void(const std::chrono::microseconds&));
+};
+
+} // namespace time
+} // namespace phosphor
+