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/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
+