blob: cfe3ae520efa7189563342c5a129c6397e6186de [file] [log] [blame]
Ed Tanous1da66f72018-07-27 16:13:37 -07001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17
18#include "node.hpp"
19
20#include <boost/container/flat_map.hpp>
21#include <experimental/filesystem>
22
23namespace redfish
24{
25
26constexpr char const *CPU_LOG_OBJECT = "com.intel.CpuDebugLog";
27constexpr char const *CPU_LOG_PATH = "/com/intel/CpuDebugLog";
28constexpr char const *CPU_LOG_IMMEDIATE_PATH =
29 "/com/intel/CpuDebugLog/Immediate";
30constexpr char const *CPU_LOG_INTERFACE = "com.intel.CpuDebugLog";
31constexpr char const *CPU_LOG_IMMEDIATE_INTERFACE =
32 "com.intel.CpuDebugLog.Immediate";
33constexpr char const *CPU_LOG_RAW_PECI_INTERFACE =
34 "com.intel.CpuDebugLog.SendRawPeci";
35
36namespace fs = std::experimental::filesystem;
37
38class LogServiceCollection : public Node
39{
40 public:
41 template <typename CrowApp>
42 LogServiceCollection(CrowApp &app) :
43 Node(app, "/redfish/v1/Managers/openbmc/LogServices/")
44 {
45 // Collections use static ID for SubRoute to add to its parent, but only
46 // load dynamic data so the duplicate static members don't get displayed
47 Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/LogServices";
48 entityPrivileges = {
49 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
50 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
51 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
52 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
53 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
54 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
55 }
56
57 private:
58 /**
59 * Functions triggers appropriate requests on DBus
60 */
61 void doGet(crow::Response &res, const crow::Request &req,
62 const std::vector<std::string> &params) override
63 {
64 // Collections don't include the static data added by SubRoute because
65 // it has a duplicate entry for members
66 res.jsonValue["@odata.type"] =
67 "#LogServiceCollection.LogServiceCollection";
68 res.jsonValue["@odata.context"] =
69 "/redfish/v1/"
70 "$metadata#LogServiceCollection.LogServiceCollection";
71 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/openbmc/LogServices";
72 res.jsonValue["Name"] = "Open BMC Log Services Collection";
73 res.jsonValue["Description"] =
74 "Collection of LogServices for this Manager";
75 nlohmann::json &logserviceArray = res.jsonValue["Members"];
76 logserviceArray = nlohmann::json::array();
77#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
78 logserviceArray.push_back(
79 {{"@odata.id", "/redfish/v1/Managers/openbmc/LogServices/CpuLog"}});
80#endif
81 res.jsonValue["Members@odata.count"] = logserviceArray.size();
82 res.end();
83 }
84};
85
86class CpuLogService : public Node
87{
88 public:
89 template <typename CrowApp>
90 CpuLogService(CrowApp &app) :
91 Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog")
92 {
93 // Set the id for SubRoute
94 Node::json["@odata.id"] =
95 "/redfish/v1/Managers/openbmc/LogServices/CpuLog";
96 entityPrivileges = {
97 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
98 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
99 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
100 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
101 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
102 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
103 }
104
105 private:
106 /**
107 * Functions triggers appropriate requests on DBus
108 */
109 void doGet(crow::Response &res, const crow::Request &req,
110 const std::vector<std::string> &params) override
111 {
112 // Copy over the static data to include the entries added by SubRoute
113 res.jsonValue = Node::json;
114 res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
115 res.jsonValue["@odata.context"] = "/redfish/v1/"
116 "$metadata#LogService.LogService";
117 res.jsonValue["Name"] = "Open BMC CPU Log Service";
118 res.jsonValue["Description"] = "CPU Log Service";
119 res.jsonValue["Id"] = "CPU Log";
120 res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
121 res.jsonValue["MaxNumberOfRecords"] = 3;
122 res.jsonValue["Actions"] = {
123 {"Oem",
124 {{"#CpuLog.Immediate",
125 {{"target",
126 "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
127 "CpuLog.Immediate"}}}}}};
128
129#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
130 res.jsonValue["Actions"]["Oem"].push_back(
131 {"#CpuLog.SendRawPeci",
132 {{"target",
133 "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
134 "CpuLog.SendRawPeci"}}});
135#endif
136 res.end();
137 }
138};
139
140class CpuLogEntryCollection : public Node
141{
142 public:
143 template <typename CrowApp>
144 CpuLogEntryCollection(CrowApp &app) :
145 Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries")
146 {
147 // Collections use static ID for SubRoute to add to its parent, but only
148 // load dynamic data so the duplicate static members don't get displayed
149 Node::json["@odata.id"] =
150 "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries";
151 entityPrivileges = {
152 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
153 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
154 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
155 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
156 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
157 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
158 }
159
160 private:
161 /**
162 * Functions triggers appropriate requests on DBus
163 */
164 void doGet(crow::Response &res, const crow::Request &req,
165 const std::vector<std::string> &params) override
166 {
167 // Collections don't include the static data added by SubRoute because
168 // it has a duplicate entry for members
169 auto getLogEntriesCallback =
170 [&res](const boost::system::error_code ec,
171 const std::vector<std::string> &resp) {
172 if (ec)
173 {
174 if (ec.value() !=
175 boost::system::errc::no_such_file_or_directory)
176 {
177 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
178 << ec.message();
179 res.result(
180 boost::beast::http::status::internal_server_error);
181 res.end();
182 return;
183 }
184 }
185 res.jsonValue["@odata.type"] =
186 "#LogEntryCollection.LogEntryCollection";
187 res.jsonValue["@odata.context"] =
188 "/redfish/v1/"
189 "$metadata#LogEntryCollection.LogEntryCollection";
190 res.jsonValue["Name"] = "Open BMC CPU Log Entries";
191 res.jsonValue["Description"] = "Collection of CPU Log Entries";
192 nlohmann::json &logentry_array = res.jsonValue["Members"];
193 logentry_array = nlohmann::json::array();
194 for (const std::string &objpath : resp)
195 {
196 // Don't list the immediate log
197 if (objpath.compare(CPU_LOG_IMMEDIATE_PATH) == 0)
198 {
199 continue;
200 }
201 std::size_t last_pos = objpath.rfind("/");
202 if (last_pos != std::string::npos)
203 {
204 logentry_array.push_back(
205 {{"@odata.id", "/redfish/v1/Managers/openbmc/"
206 "LogServices/CpuLog/Entries/" +
207 objpath.substr(last_pos + 1)}});
208 }
209 }
210 res.jsonValue["Members@odata.count"] = logentry_array.size();
211 res.end();
212 };
213 crow::connections::systemBus->async_method_call(
214 std::move(getLogEntriesCallback),
215 "xyz.openbmc_project.ObjectMapper",
216 "/xyz/openbmc_project/object_mapper",
217 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
218 std::array<const char *, 1>{CPU_LOG_INTERFACE});
219 }
220};
221
222std::string getLogCreatedTime(const nlohmann::json &cpuLog)
223{
224 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
225 if (metaIt != cpuLog.end())
226 {
227 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
228 if (tsIt != metaIt->end())
229 {
230 const std::string *logTime = tsIt->get_ptr<const std::string *>();
231 if (logTime != nullptr)
232 {
233 return *logTime;
234 }
235 }
236 }
237 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
238
239 return std::string();
240}
241
242class CpuLogEntry : public Node
243{
244 public:
245 CpuLogEntry(CrowApp &app) :
246 Node(app,
247 "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries/<str>/",
248 std::string())
249 {
250 entityPrivileges = {
251 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
252 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
253 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
254 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
255 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
256 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
257 }
258
259 private:
260 void doGet(crow::Response &res, const crow::Request &req,
261 const std::vector<std::string> &params) override
262 {
263 if (params.size() != 1)
264 {
265 res.result(boost::beast::http::status::internal_server_error);
266 res.end();
267 return;
268 }
269 const uint8_t log_id = std::atoi(params[0].c_str());
270 auto getStoredLogCallback = [&res,
271 log_id](const boost::system::error_code ec,
272 const sdbusplus::message::variant<
273 std::string> &resp) {
274 if (ec)
275 {
276 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
277 res.result(boost::beast::http::status::internal_server_error);
278 res.end();
279 return;
280 }
281 const std::string *log = mapbox::getPtr<const std::string>(resp);
282 if (log == nullptr)
283 {
284 res.result(boost::beast::http::status::internal_server_error);
285 res.end();
286 return;
287 }
288 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
289 if (j.is_discarded())
290 {
291 res.result(boost::beast::http::status::internal_server_error);
292 res.end();
293 return;
294 }
295 std::string t = getLogCreatedTime(j);
296 res.jsonValue = {
297 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
298 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
299 {"@odata.id",
300 "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries/" +
301 std::to_string(log_id)},
302 {"Name", "CPU Debug Log"},
303 {"Id", log_id},
304 {"EntryType", "Oem"},
305 {"OemRecordFormat", "Intel CPU Log"},
306 {"Oem", {{"Intel", std::move(j)}}},
307 {"Created", std::move(t)}};
308 res.end();
309 };
310 crow::connections::systemBus->async_method_call(
311 std::move(getStoredLogCallback), CPU_LOG_OBJECT,
312 CPU_LOG_PATH + std::string("/") + std::to_string(log_id),
313 "org.freedesktop.DBus.Properties", "Get", CPU_LOG_INTERFACE, "Log");
314 }
315};
316
317class ImmediateCpuLog : public Node
318{
319 public:
320 ImmediateCpuLog(CrowApp &app) :
321 Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
322 "CpuLog.Immediate")
323 {
324 entityPrivileges = {
325 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
326 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
327 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
328 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
329 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
330 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
331 }
332
333 private:
334 void doPost(crow::Response &res, const crow::Request &req,
335 const std::vector<std::string> &params) override
336 {
337 static std::unique_ptr<sdbusplus::bus::match::match>
338 immediateLogMatcher;
339
340 // Only allow one Immediate Log request at a time
341 if (immediateLogMatcher != nullptr)
342 {
343 res.addHeader("Retry-After", "30");
344 res.result(boost::beast::http::status::service_unavailable);
345 messages::addMessageToJson(
346 res.jsonValue, messages::serviceTemporarilyUnavailable("30"),
347 "/CpuLog.Immediate");
348 res.end();
349 return;
350 }
351 // Make this static so it survives outside this method
352 static boost::asio::deadline_timer timeout(*req.ioService);
353
354 timeout.expires_from_now(boost::posix_time::seconds(30));
355 timeout.async_wait([&res](const boost::system::error_code &ec) {
356 immediateLogMatcher = nullptr;
357 if (ec)
358 {
359 // operation_aborted is expected if timer is canceled before
360 // completion.
361 if (ec != boost::asio::error::operation_aborted)
362 {
363 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
364 }
365 return;
366 }
367 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
368
369 res.result(boost::beast::http::status::internal_server_error);
370 res.end();
371 });
372
373 auto immediateLogMatcherCallback = [&res](
374 sdbusplus::message::message &m) {
375 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
376 boost::system::error_code ec;
377 timeout.cancel(ec);
378 if (ec)
379 {
380 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
381 }
382 sdbusplus::message::object_path obj_path;
383 boost::container::flat_map<
384 std::string,
385 boost::container::flat_map<
386 std::string, sdbusplus::message::variant<std::string>>>
387 interfaces_added;
388 m.read(obj_path, interfaces_added);
389 const std::string *log = mapbox::getPtr<const std::string>(
390 interfaces_added[CPU_LOG_INTERFACE]["Log"]);
391 if (log == nullptr)
392 {
393 res.result(boost::beast::http::status::internal_server_error);
394 res.end();
395 // Careful with immediateLogMatcher. It is a unique_ptr to the
396 // match object inside which this lambda is executing. Once it
397 // is set to nullptr, the match object will be destroyed and the
398 // lambda will lose its context, including res, so it needs to
399 // be the last thing done.
400 immediateLogMatcher = nullptr;
401 return;
402 }
403 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
404 if (j.is_discarded())
405 {
406 res.result(boost::beast::http::status::internal_server_error);
407 res.end();
408 // Careful with immediateLogMatcher. It is a unique_ptr to the
409 // match object inside which this lambda is executing. Once it
410 // is set to nullptr, the match object will be destroyed and the
411 // lambda will lose its context, including res, so it needs to
412 // be the last thing done.
413 immediateLogMatcher = nullptr;
414 return;
415 }
416 std::string t = getLogCreatedTime(j);
417 res.jsonValue = {
418 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
419 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
420 {"Name", "CPU Debug Log"},
421 {"EntryType", "Oem"},
422 {"OemRecordFormat", "Intel CPU Log"},
423 {"Oem", {{"Intel", std::move(j)}}},
424 {"Created", std::move(t)}};
425 res.end();
426 // Careful with immediateLogMatcher. It is a unique_ptr to the
427 // match object inside which this lambda is executing. Once it is
428 // set to nullptr, the match object will be destroyed and the lambda
429 // will lose its context, including res, so it needs to be the last
430 // thing done.
431 immediateLogMatcher = nullptr;
432 };
433 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
434 *crow::connections::systemBus,
435 sdbusplus::bus::match::rules::interfacesAdded() +
436 sdbusplus::bus::match::rules::argNpath(0,
437 CPU_LOG_IMMEDIATE_PATH),
438 std::move(immediateLogMatcherCallback));
439
440 auto generateImmediateLogCallback =
441 [&res](const boost::system::error_code ec,
442 const std::string &resp) {
443 if (ec)
444 {
445 if (ec.value() ==
446 boost::system::errc::operation_not_supported)
447 {
448 messages::addMessageToJson(
449 res.jsonValue, messages::resourceInStandby(),
450 "/CpuLog.Immediate");
451 res.result(
452 boost::beast::http::status::service_unavailable);
453 }
454 else
455 {
456 res.result(
457 boost::beast::http::status::internal_server_error);
458 }
459 res.end();
460 boost::system::error_code timeoutec;
461 timeout.cancel(timeoutec);
462 if (timeoutec)
463 {
464 BMCWEB_LOG_ERROR << "error canceling timer "
465 << timeoutec;
466 }
467 immediateLogMatcher = nullptr;
468 return;
469 }
470 };
471 crow::connections::systemBus->async_method_call(
472 std::move(generateImmediateLogCallback), CPU_LOG_OBJECT,
473 CPU_LOG_PATH, CPU_LOG_IMMEDIATE_INTERFACE, "GenerateImmediateLog");
474 }
475};
476
477class SendRawPeci : public Node
478{
479 public:
480 SendRawPeci(CrowApp &app) :
481 Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
482 "CpuLog.SendRawPeci")
483 {
484 entityPrivileges = {
485 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
486 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
487 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
488 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
489 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
490 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
491 }
492
493 private:
494 void doPost(crow::Response &res, const crow::Request &req,
495 const std::vector<std::string> &params) override
496 {
497 // Get the Raw PECI command from the request
498 nlohmann::json rawPeciCmd;
499 if (!json_util::processJsonFromRequest(res, req, rawPeciCmd))
500 {
501 return;
502 }
503 // Get the Client Address from the request
504 nlohmann::json::const_iterator caIt = rawPeciCmd.find("ClientAddress");
505 if (caIt == rawPeciCmd.end())
506 {
507 messages::addMessageToJson(
508 res.jsonValue, messages::propertyMissing("ClientAddress"),
509 "/ClientAddress");
510 res.result(boost::beast::http::status::bad_request);
511 res.end();
512 return;
513 }
514 const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
515 if (ca == nullptr)
516 {
517 messages::addMessageToJson(
518 res.jsonValue,
519 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"),
520 "/ClientAddress");
521 res.result(boost::beast::http::status::bad_request);
522 res.end();
523 return;
524 }
525 // Get the Read Length from the request
526 const uint8_t clientAddress = static_cast<uint8_t>(*ca);
527 nlohmann::json::const_iterator rlIt = rawPeciCmd.find("ReadLength");
528 if (rlIt == rawPeciCmd.end())
529 {
530 messages::addMessageToJson(res.jsonValue,
531 messages::propertyMissing("ReadLength"),
532 "/ReadLength");
533 res.result(boost::beast::http::status::bad_request);
534 res.end();
535 return;
536 }
537 const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
538 if (rl == nullptr)
539 {
540 messages::addMessageToJson(
541 res.jsonValue,
542 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"),
543 "/ReadLength");
544 res.result(boost::beast::http::status::bad_request);
545 res.end();
546 return;
547 }
548 // Get the PECI Command from the request
549 const uint32_t readLength = static_cast<uint32_t>(*rl);
550 nlohmann::json::const_iterator pcIt = rawPeciCmd.find("PECICommand");
551 if (pcIt == rawPeciCmd.end())
552 {
553 messages::addMessageToJson(res.jsonValue,
554 messages::propertyMissing("PECICommand"),
555 "/PECICommand");
556 res.result(boost::beast::http::status::bad_request);
557 res.end();
558 return;
559 }
560 std::vector<uint8_t> peciCommand;
561 for (auto pc : *pcIt)
562 {
563 const uint64_t *val = pc.get_ptr<const uint64_t *>();
564 if (val == nullptr)
565 {
566 messages::addMessageToJson(
567 res.jsonValue,
568 messages::propertyValueTypeError(
569 pc.dump(),
570 "PECICommand/" + std::to_string(peciCommand.size())),
571 "/PECICommand");
572 res.result(boost::beast::http::status::bad_request);
573 res.end();
574 return;
575 }
576 peciCommand.push_back(static_cast<uint8_t>(*val));
577 }
578 // Callback to return the Raw PECI response
579 auto sendRawPeciCallback = [&res](const boost::system::error_code ec,
580 const std::vector<uint8_t> &resp) {
581 if (ec)
582 {
583 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
584 << ec.message();
585 res.result(boost::beast::http::status::internal_server_error);
586 res.end();
587 return;
588 }
589 res.jsonValue = {{"Name", "PECI Command Response"},
590 {"PECIResponse", resp}};
591 res.end();
592 };
593 // Call the SendRawPECI command with the provided data
594 crow::connections::systemBus->async_method_call(
595 std::move(sendRawPeciCallback), CPU_LOG_OBJECT, CPU_LOG_PATH,
596 CPU_LOG_RAW_PECI_INTERFACE, "SendRawPeci", clientAddress,
597 readLength, peciCommand);
598 }
599};
600
601} // namespace redfish