-
Notifications
You must be signed in to change notification settings - Fork 22
/
scheduled_host_transition.cpp
318 lines (277 loc) · 9.43 KB
/
scheduled_host_transition.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#include "scheduled_host_transition.hpp"
#include "utils.hpp"
#include "xyz/openbmc_project/State/Host/server.hpp"
#include <sys/timerfd.h>
#include <unistd.h>
#include <cereal/archives/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/ScheduledTime/error.hpp>
#include <xyz/openbmc_project/State/Host/error.hpp>
#include <chrono>
#include <filesystem>
#include <fstream>
// 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
namespace phosphor
{
namespace state
{
namespace manager
{
PHOSPHOR_LOG2_USING;
namespace fs = std::filesystem;
using namespace std::chrono;
using namespace phosphor::logging;
using namespace xyz::openbmc_project::ScheduledTime;
using InvalidTimeError =
sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
using HostTransition =
sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition;
using HostState = sdbusplus::server::xyz::openbmc_project::state::Host;
constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
{
info("A scheduled host transition request has been made for {TIME}", "TIME",
value);
if (value == 0)
{
// 0 means the function Scheduled Host Transition is disabled
// Stop the timer if it's running
if (timer.isEnabled())
{
timer.setEnabled(false);
debug(
"scheduledTime: The function Scheduled Host Transition is disabled.");
}
}
else
{
auto deltaTime = seconds(value) - getTime();
if (deltaTime < seconds(0))
{
error(
"Scheduled time is earlier than current time. Fail to schedule host transition.");
elog<InvalidTimeError>(
InvalidTime::REASON("Scheduled time is in the past"));
}
else
{
// Start a timer to do host transition at scheduled time
timer.restart(deltaTime);
}
}
// Set scheduledTime
HostTransition::scheduledTime(value);
// Store scheduled values
serializeScheduledValues();
return value;
}
seconds ScheduledHostTransition::getTime()
{
auto now = system_clock::now();
return duration_cast<seconds>(now.time_since_epoch());
}
void ScheduledHostTransition::hostTransition()
{
auto hostName = std::string(HostState::namespace_path::host) +
std::to_string(id);
std::string hostPath =
sdbusplus::message::object_path(HostState::namespace_path::value) /
hostName;
auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
"REQUESTED_TRANSITION", reqTrans);
utils::setProperty(bus, hostPath, HostState::interface, PROPERTY_TRANSITION,
reqTrans);
// Set RestartCause to indicate this transition is occurring due to a
// scheduled host transition as long as it's not an off request
if (HostTransition::scheduledTransition() != HostState::Transition::Off)
{
info("Set RestartCause to scheduled power on reason");
auto resCause =
convertForMessage(HostState::RestartCause::ScheduledPowerOn);
utils::setProperty(bus, hostPath, HostState::interface,
PROPERTY_RESTART_CAUSE, resCause);
}
}
void ScheduledHostTransition::callback()
{
// Stop timer, since we need to do host transition once only
timer.setEnabled(false);
hostTransition();
// Set scheduledTime to 0 to disable host transition and update scheduled
// values
scheduledTime(0);
}
void ScheduledHostTransition::initialize()
{
// Subscribe time change event
// Choose the MAX time that is possible to avoid misfires.
constexpr itimerspec maxTime = {
{0, 0}, // it_interval
{system_clock::duration::max().count(), 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;
error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
eno, "RC", 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;
error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
"RC", 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;
error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
"RC", r);
throw std::system_error(eno, std::system_category());
}
timeChangeEventSource.reset(es);
}
ScheduledHostTransition::~ScheduledHostTransition()
{
close(timeFd);
}
void ScheduledHostTransition::handleTimeUpdates()
{
// Stop the timer if it's running.
// Don't return directly when timer is stopped, because the timer is always
// disabled after the BMC is rebooted.
if (timer.isEnabled())
{
timer.setEnabled(false);
}
// Get scheduled time
auto schedTime = HostTransition::scheduledTime();
if (schedTime == 0)
{
debug(
"handleTimeUpdates: The function Scheduled Host Transition is disabled.");
return;
}
auto deltaTime = seconds(schedTime) - getTime();
if (deltaTime <= seconds(0))
{
try
{
hostTransition();
}
catch (const sdbusplus::exception_t& e)
{
using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state::
host::BMCNotReady;
// If error indicates BMC is not at Ready error then reschedule for
// 60s later
if ((e.name() != nullptr) &&
(e.name() == std::string_view(BMCNotReady::errName)))
{
warning(
"BMC is not at ready, reschedule transition request for 60s");
timer.restart(seconds(60));
return;
}
else
{
throw;
}
}
// Set scheduledTime to 0 to disable host transition and update
// scheduled values
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)
{}
debug("BMC system time is changed");
schedHostTran->handleTimeUpdates();
return 0;
}
void ScheduledHostTransition::serializeScheduledValues()
{
fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
std::ofstream os(path.c_str(), std::ios::binary);
cereal::JSONOutputArchive oarchive(os);
oarchive(HostTransition::scheduledTime(),
HostTransition::scheduledTransition());
}
bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
Transition& trans)
{
fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
try
{
if (fs::exists(path))
{
std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
cereal::JSONInputArchive iarchive(is);
iarchive(time, trans);
return true;
}
}
catch (const std::exception& e)
{
error("deserialize exception: {ERROR}", "ERROR", e);
fs::remove(path);
}
return false;
}
void ScheduledHostTransition::restoreScheduledValues()
{
uint64_t time;
Transition trans;
if (!deserializeScheduledValues(time, trans))
{
// set to default value
HostTransition::scheduledTime(0);
HostTransition::scheduledTransition(Transition::On);
}
else
{
HostTransition::scheduledTime(time);
HostTransition::scheduledTransition(trans);
// Rebooting BMC is something like the BMC time is changed,
// so go on with the same process as BMC time changed.
handleTimeUpdates();
}
}
} // namespace manager
} // namespace state
} // namespace phosphor