sched-host-tran: handle with BMC time changing
Handle with different processes when BMC time is changed after scheduled time
is set.
Tested:
Case1: BMC time is changed to be later than current time but still earlier than
scheduled time
1. Get current time
# date
Tue Feb 25 07:07:44 UTC 2020
# date +%s
1582614271
2. Schedule time, do host transition after at 07:20:00 around
# busctl get-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime
t 1582615256
3. Change BMC time to 07:19:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582615136000000
# date
Tue Feb 25 07:19:20 UTC 2020
# date +%s
1582615187
4. Host transition is done after 1 minute around,
instead of waiting 13 mins around.
Case2: BMC time is changed after scheduled time is reached
Following Case1, the scheduled time is reached already,
1. Change BMC time to 07:10:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582614600000000
2. APP shows "The function Scheduled Host Transition is disabled", because
the scheduled time is reached already and the scheduled time has been set
to 0 after host transition is triggered.
Case3: BMC time is changed to be bigger than scheduled time before scheduled
time is reached
1. Set scheduled time 07:08:00 around
# busctl set-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime t 1582787314
2. Change BMC time to 07:10:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582787434000000
3. It will do host transition as requested.
Case4: BMC time is changed to be earlier than current time
1. Set scheduled time 07:10:00 around
# busctl set-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime t 1582787434
2. Change BMC time to 07:08:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582787314000000
3. App will wait 2 minutes more to do host transition.
Change-Id: I23228be944d1b2f71161317228c8b16d7f5ca4eb
Signed-off-by: Carol Wang <wangkair@cn.ibm.com>
diff --git a/scheduled_host_transition.cpp b/scheduled_host_transition.cpp
index b65d097..9fdd32b 100644
--- a/scheduled_host_transition.cpp
+++ b/scheduled_host_transition.cpp
@@ -5,6 +5,17 @@
#include <phosphor-logging/log.hpp>
#include <xyz/openbmc_project/ScheduledTime/error.hpp>
#include <chrono>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+// Need 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
{
@@ -39,7 +50,8 @@
timer.setEnabled(false);
}
- log<level::INFO>("The function Scheduled Host Transition is disabled.");
+ log<level::INFO>("scheduledTime: The function Scheduled Host "
+ "Transition is disabled.");
}
else
{
@@ -141,6 +153,114 @@
HostTransition::scheduledTime(0);
}
+void ScheduledHostTransition::initialize()
+{
+ // 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
+ };
+
+ // Create and operate on a timer that delivers timer expiration
+ // notifications via a file descriptor.
+ timeFd = timerfd_create(CLOCK_REALTIME, 0);
+ if (timeFd == -1)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", eno),
+ entry("RC=%d", timeFd));
+ throw std::system_error(eno, std::system_category());
+ }
+
+ // Starts the timer referred to by the file descriptor fd.
+ // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
+ // and the clock for this timer is CLOCK_REALTIME, then mark this timer
+ // as cancelable if the real-time clock undergoes a discontinuous change.
+ // In this way, we can monitor whether BMC time is changed or not.
+ auto r = timerfd_settime(
+ timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
+ if (r != 0)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", eno),
+ entry("RC=%d", r));
+ throw std::system_error(eno, std::system_category());
+ }
+
+ sd_event_source* es;
+ // Add a new I/O event source to an event loop. onTimeChange will be called
+ // when the event source is triggered.
+ r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
+ if (r < 0)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to add event", entry("ERRNO=%d", eno),
+ entry("RC=%d", r));
+ throw std::system_error(eno, std::system_category());
+ }
+ timeChangeEventSource.reset(es);
+}
+
+ScheduledHostTransition::~ScheduledHostTransition()
+{
+ close(timeFd);
+}
+
+void ScheduledHostTransition::handleTimeUpdates()
+{
+ if (!timer.isEnabled())
+ {
+ return;
+ }
+ // Stop the timer if it's running
+ timer.setEnabled(false);
+
+ // Get scheduled time
+ auto schedTime = HostTransition::scheduledTime();
+
+ if (schedTime == 0)
+ {
+ log<level::INFO>("handleTimeUpdates: The function Scheduled Host "
+ "Transition is disabled.");
+ return;
+ }
+
+ auto deltaTime = seconds(schedTime) - getTime();
+ if (deltaTime <= seconds(0))
+ {
+ // When BMC time is changed to be later than scheduled time, check the
+ // state of host transition to decide whether need to do host transition
+ hostTransition();
+ // Set scheduledTime to 0 to disable host transition
+ HostTransition::scheduledTime(0);
+ }
+ else
+ {
+ // Start a timer to do host transition at scheduled time
+ timer.restart(deltaTime);
+ }
+}
+
+int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
+ uint32_t /* revents */,
+ void* userdata)
+{
+ auto schedHostTran = static_cast<ScheduledHostTransition*>(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");
+ schedHostTran->handleTimeUpdates();
+
+ return 0;
+}
+
} // namespace manager
} // namespace state
} // namespace phosphor
diff --git a/scheduled_host_transition.hpp b/scheduled_host_transition.hpp
index 1cb2591..a17ca3b 100644
--- a/scheduled_host_transition.hpp
+++ b/scheduled_host_transition.hpp
@@ -32,12 +32,13 @@
ScheduledHostTransition(sdbusplus::bus::bus& bus, const char* objPath,
const sdeventplus::Event& event) :
ScheduledHostTransitionInherit(bus, objPath),
- bus(bus),
+ bus(bus), event(event),
timer(event, std::bind(&ScheduledHostTransition::callback, this))
{
+ initialize();
}
- ~ScheduledHostTransition() = default;
+ ~ScheduledHostTransition();
/**
* @brief Handle with scheduled time
@@ -55,9 +56,15 @@
/** @brief sdbusplus bus client connection */
sdbusplus::bus::bus& bus;
+ /** @brief sdbusplus event */
+ const sdeventplus::Event& event;
+
/** @brief Timer used for host transition with seconds */
sdeventplus::utility::Timer<sdeventplus::ClockId::RealTime> timer;
+ /** @brief The fd for time change event */
+ int timeFd = -1;
+
/** @brief Get current time
*
* @return - return current epoch time
@@ -73,6 +80,37 @@
/** @brief Used by the timer to do host transition */
void callback();
+
+ /** @brief Initialize timerFd related resource */
+ void initialize();
+
+ /** @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 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 Handle with the process when bmc time is changed*/
+ void handleTimeUpdates();
};
} // namespace manager
} // namespace state
diff --git a/test/test_scheduled_host_transition.cpp b/test/test_scheduled_host_transition.cpp
index 58e7aea..f148b59 100644
--- a/test/test_scheduled_host_transition.cpp
+++ b/test/test_scheduled_host_transition.cpp
@@ -17,6 +17,8 @@
using namespace std::chrono;
using InvalidTimeError =
sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
+using HostTransition =
+ sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
class TestScheduledHostTransition : public testing::Test
{
@@ -42,6 +44,11 @@
{
return scheduledHostTransition.timer.isEnabled();
}
+
+ void bmcTimeChange()
+ {
+ scheduledHostTransition.handleTimeUpdates();
+ }
};
TEST_F(TestScheduledHostTransition, disableHostTransition)
@@ -78,6 +85,32 @@
EXPECT_EQ(scheduledHostTransition.scheduledTransition(), Transition::Off);
}
+TEST_F(TestScheduledHostTransition, bmcTimeChangeWithDisabledHostTransition)
+{
+ // Disable host transition
+ scheduledHostTransition.scheduledTime(0);
+ bmcTimeChange();
+ // Check timer
+ EXPECT_FALSE(isTimerEnabled());
+ // Check scheduled time
+ EXPECT_EQ(scheduledHostTransition.HostTransition::scheduledTime(), 0);
+}
+
+TEST_F(TestScheduledHostTransition, bmcTimeChangeBackward)
+{
+ // Current time is earlier than scheduled time due to BMC time changing
+ uint64_t schTime =
+ static_cast<uint64_t>((getCurrentTime() + seconds(60)).count());
+ // Set scheduled time, which is the same as bmc time is changed.
+ // But can't use this method to write another case like
+ // bmcTimeChangeForward, because set a scheduled time earlier than current
+ // time will throw an error.
+ scheduledHostTransition.scheduledTime(schTime);
+ bmcTimeChange();
+ // Check timer
+ EXPECT_TRUE(isTimerEnabled());
+}
+
} // namespace manager
} // namespace state
} // namespace phosphor