blob: 2dda75247eea9a2bdfeb4e07138a8a52cd086727 [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
Jason M. Billse1f26342018-07-18 12:12:00 -070020#include <systemd/sd-journal.h>
21
Ed Tanous1da66f72018-07-27 16:13:37 -070022#include <boost/container/flat_map.hpp>
Jason M. Billse1f26342018-07-18 12:12:00 -070023#include <boost/utility/string_view.hpp>
Ed Tanous1da66f72018-07-27 16:13:37 -070024#include <experimental/filesystem>
25
26namespace redfish
27{
28
Ed Tanous4ed77cd2018-10-15 08:08:07 -070029constexpr char const *cpuLogObject = "com.intel.CpuDebugLog";
30constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog";
31constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate";
32constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
33constexpr char const *cpuLogImmediateInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070034 "com.intel.CpuDebugLog.Immediate";
Jason M. Billse1f26342018-07-18 12:12:00 -070035constexpr char const *cpuLogRawPECIInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070036 "com.intel.CpuDebugLog.SendRawPeci";
37
38namespace fs = std::experimental::filesystem;
39
Jason M. Bills16428a12018-11-02 12:42:29 -070040static int getJournalMetadata(sd_journal *journal,
41 const boost::string_view &field,
42 boost::string_view &contents)
43{
44 const char *data = nullptr;
45 size_t length = 0;
46 int ret = 0;
47 // Get the metadata from the requested field of the journal entry
48 ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
49 &length);
50 if (ret < 0)
51 {
52 return ret;
53 }
54 contents = boost::string_view(data, length);
55 // Only use the content after the "=" character.
56 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
57 return ret;
58}
59
60static int getJournalMetadata(sd_journal *journal,
61 const boost::string_view &field, const int &base,
62 int &contents)
63{
64 int ret = 0;
65 boost::string_view metadata;
66 // Get the metadata from the requested field of the journal entry
67 ret = getJournalMetadata(journal, field, metadata);
68 if (ret < 0)
69 {
70 return ret;
71 }
72 contents = strtol(metadata.data(), nullptr, base);
73 return ret;
74}
75
76static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
77{
78 int ret = 0;
79 uint64_t timestamp = 0;
80 ret = sd_journal_get_realtime_usec(journal, &timestamp);
81 if (ret < 0)
82 {
83 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
84 << strerror(-ret);
85 return false;
86 }
87 time_t t =
88 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
89 struct tm *loctime = localtime(&t);
90 char entryTime[64] = {};
91 if (NULL != loctime)
92 {
93 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
94 }
95 // Insert the ':' into the timezone
96 boost::string_view t1(entryTime);
97 boost::string_view t2(entryTime);
98 if (t1.size() > 2 && t2.size() > 2)
99 {
100 t1.remove_suffix(2);
101 t2.remove_prefix(t2.size() - 2);
102 }
103 entryTimestamp = t1.to_string() + ":" + t2.to_string();
104 return true;
105}
106
107static bool getSkipParam(crow::Response &res, const crow::Request &req,
108 long &skip)
109{
110 char *skipParam = req.urlParams.get("$skip");
111 if (skipParam != nullptr)
112 {
113 char *ptr = nullptr;
114 skip = std::strtol(skipParam, &ptr, 10);
115 if (*skipParam == '\0' || *ptr != '\0')
116 {
117
118 messages::queryParameterValueTypeError(res, std::string(skipParam),
119 "$skip");
120 return false;
121 }
122 if (skip < 0)
123 {
124
125 messages::queryParameterOutOfRange(res, std::to_string(skip),
126 "$skip", "greater than 0");
127 return false;
128 }
129 }
130 return true;
131}
132
133static constexpr const long maxEntriesPerPage = 1000;
134static bool getTopParam(crow::Response &res, const crow::Request &req,
135 long &top)
136{
137 char *topParam = req.urlParams.get("$top");
138 if (topParam != nullptr)
139 {
140 char *ptr = nullptr;
141 top = std::strtol(topParam, &ptr, 10);
142 if (*topParam == '\0' || *ptr != '\0')
143 {
144 messages::queryParameterValueTypeError(res, std::string(topParam),
145 "$top");
146 return false;
147 }
148 if (top < 1 || top > maxEntriesPerPage)
149 {
150
151 messages::queryParameterOutOfRange(
152 res, std::to_string(top), "$top",
153 "1-" + std::to_string(maxEntriesPerPage));
154 return false;
155 }
156 }
157 return true;
158}
159
160static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
161{
162 int ret = 0;
163 static uint64_t prevTs = 0;
164 static int index = 0;
165 // Get the entry timestamp
166 uint64_t curTs = 0;
167 ret = sd_journal_get_realtime_usec(journal, &curTs);
168 if (ret < 0)
169 {
170 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
171 << strerror(-ret);
172 return false;
173 }
174 // If the timestamp isn't unique, increment the index
175 if (curTs == prevTs)
176 {
177 index++;
178 }
179 else
180 {
181 // Otherwise, reset it
182 index = 0;
183 }
184 // Save the timestamp
185 prevTs = curTs;
186
187 entryID = std::to_string(curTs);
188 if (index > 0)
189 {
190 entryID += "_" + std::to_string(index);
191 }
192 return true;
193}
194
195static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
196 uint64_t &timestamp, uint16_t &index)
197{
198 if (entryID.empty())
199 {
200 return false;
201 }
202 // Convert the unique ID back to a timestamp to find the entry
203 boost::string_view tsStr(entryID);
204
205 auto underscorePos = tsStr.find("_");
206 if (underscorePos != tsStr.npos)
207 {
208 // Timestamp has an index
209 tsStr.remove_suffix(tsStr.size() - underscorePos);
210 boost::string_view indexStr(entryID);
211 indexStr.remove_prefix(underscorePos + 1);
212 std::size_t pos;
213 try
214 {
215 index = std::stoul(indexStr.to_string(), &pos);
216 }
217 catch (std::invalid_argument)
218 {
219 messages::resourceMissingAtURI(res, entryID);
220 return false;
221 }
222 catch (std::out_of_range)
223 {
224 messages::resourceMissingAtURI(res, entryID);
225 return false;
226 }
227 if (pos != indexStr.size())
228 {
229 messages::resourceMissingAtURI(res, entryID);
230 return false;
231 }
232 }
233 // Timestamp has no index
234 std::size_t pos;
235 try
236 {
237 timestamp = std::stoull(tsStr.to_string(), &pos);
238 }
239 catch (std::invalid_argument)
240 {
241 messages::resourceMissingAtURI(res, entryID);
242 return false;
243 }
244 catch (std::out_of_range)
245 {
246 messages::resourceMissingAtURI(res, entryID);
247 return false;
248 }
249 if (pos != tsStr.size())
250 {
251 messages::resourceMissingAtURI(res, entryID);
252 return false;
253 }
254 return true;
255}
256
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800257class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700258{
259 public:
260 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800261 SystemLogServiceCollection(CrowApp &app) :
262 Node(app, "/redfish/v1/Systems/<str>/LogServices/", std::string())
263 {
264 entityPrivileges = {
265 {boost::beast::http::verb::get, {{"Login"}}},
266 {boost::beast::http::verb::head, {{"Login"}}},
267 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
268 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
269 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
270 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
271 }
272
273 private:
274 /**
275 * Functions triggers appropriate requests on DBus
276 */
277 void doGet(crow::Response &res, const crow::Request &req,
278 const std::vector<std::string> &params) override
279 {
280 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
281 const std::string &name = params[0];
282 // Collections don't include the static data added by SubRoute because
283 // it has a duplicate entry for members
284 asyncResp->res.jsonValue["@odata.type"] =
285 "#LogServiceCollection.LogServiceCollection";
286 asyncResp->res.jsonValue["@odata.context"] =
287 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
288 asyncResp->res.jsonValue["@odata.id"] =
289 "/redfish/v1/Systems/" + name + "/LogServices";
290 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
291 asyncResp->res.jsonValue["Description"] =
292 "Collection of LogServices for this Computer System";
293 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
294 logServiceArray = nlohmann::json::array();
295 logServiceArray.push_back({{"@odata.id", "/redfish/v1/Systems/" + name +
296 "/LogServices/EventLog"}});
297 asyncResp->res.jsonValue["Members@odata.count"] =
298 logServiceArray.size();
299 }
300};
301
302class EventLogService : public Node
303{
304 public:
305 template <typename CrowApp>
306 EventLogService(CrowApp &app) :
307 Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/",
308 std::string())
309 {
310 entityPrivileges = {
311 {boost::beast::http::verb::get, {{"Login"}}},
312 {boost::beast::http::verb::head, {{"Login"}}},
313 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
314 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
316 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
317 }
318
319 private:
320 void doGet(crow::Response &res, const crow::Request &req,
321 const std::vector<std::string> &params) override
322 {
323 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
324
325 const std::string &name = params[0];
326 asyncResp->res.jsonValue["@odata.id"] =
327 "/redfish/v1/Systems/" + name + "/LogServices/EventLog";
328 asyncResp->res.jsonValue["@odata.type"] =
329 "#LogService.v1_1_0.LogService";
330 asyncResp->res.jsonValue["@odata.context"] =
331 "/redfish/v1/$metadata#LogService.LogService";
332 asyncResp->res.jsonValue["Name"] = "Event Log Service";
333 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
334 asyncResp->res.jsonValue["Id"] = "Event Log";
335 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
336 asyncResp->res.jsonValue["Entries"] = {
337 {"@odata.id",
338 "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries"}};
339 }
340};
341
342static int fillEventLogEntryJson(const std::string &systemName,
343 const std::string &bmcLogEntryID,
344 const boost::string_view &messageID,
345 sd_journal *journal,
346 nlohmann::json &bmcLogEntryJson)
347{
348 // Get the Log Entry contents
349 int ret = 0;
350
351 boost::string_view msg;
352 ret = getJournalMetadata(journal, "MESSAGE", msg);
353 if (ret < 0)
354 {
355 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
356 return 1;
357 }
358
359 // Get the severity from the PRIORITY field
360 int severity = 8; // Default to an invalid priority
361 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
362 if (ret < 0)
363 {
364 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
365 return 1;
366 }
367
368 // Get the MessageArgs from the journal entry by finding all of the
369 // REDFISH_MESSAGE_ARG_x fields
370 const void *data;
371 size_t length;
372 std::vector<std::string> messageArgs;
373 SD_JOURNAL_FOREACH_DATA(journal, data, length)
374 {
375 boost::string_view field(static_cast<const char *>(data), length);
376 if (field.starts_with("REDFISH_MESSAGE_ARG_"))
377 {
378 // Get the Arg number from the field name
379 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1);
380 if (field.empty())
381 {
382 continue;
383 }
384 int argNum = std::strtoul(field.data(), nullptr, 10);
385 if (argNum == 0)
386 {
387 continue;
388 }
389 // Get the Arg value after the "=" character.
390 field.remove_prefix(std::min(field.find("=") + 1, field.size()));
391 // Make sure we have enough space in messageArgs
392 if (argNum > messageArgs.size())
393 {
394 messageArgs.resize(argNum);
395 }
396 messageArgs[argNum - 1] = field.to_string();
397 }
398 }
399
400 // Get the Created time from the timestamp
401 std::string entryTimeStr;
402 if (!getEntryTimestamp(journal, entryTimeStr))
403 {
404 return 1;
405 }
406
407 // Fill in the log entry with the gathered data
408 bmcLogEntryJson = {
409 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
410 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
411 {"@odata.id", "/redfish/v1/Systems/" + systemName +
412 "/LogServices/EventLog/Entries/" + bmcLogEntryID},
413 {"Name", "System Event Log Entry"},
414 {"Id", bmcLogEntryID},
415 {"Message", msg},
416 {"MessageId", messageID},
417 {"MessageArgs", std::move(messageArgs)},
418 {"EntryType", "Event"},
419 {"Severity",
420 severity <= 2 ? "Critical"
421 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
422 {"Created", std::move(entryTimeStr)}};
423 return 0;
424}
425
426class EventLogEntryCollection : public Node
427{
428 public:
429 template <typename CrowApp>
430 EventLogEntryCollection(CrowApp &app) :
431 Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/",
432 std::string())
433 {
434 entityPrivileges = {
435 {boost::beast::http::verb::get, {{"Login"}}},
436 {boost::beast::http::verb::head, {{"Login"}}},
437 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
438 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
439 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
440 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
441 }
442
443 private:
444 void doGet(crow::Response &res, const crow::Request &req,
445 const std::vector<std::string> &params) override
446 {
447 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
448 long skip = 0;
449 long top = maxEntriesPerPage; // Show max entries by default
450 if (!getSkipParam(asyncResp->res, req, skip))
451 {
452 return;
453 }
454 if (!getTopParam(asyncResp->res, req, top))
455 {
456 return;
457 }
458 const std::string &name = params[0];
459 // Collections don't include the static data added by SubRoute because
460 // it has a duplicate entry for members
461 asyncResp->res.jsonValue["@odata.type"] =
462 "#LogEntryCollection.LogEntryCollection";
463 asyncResp->res.jsonValue["@odata.context"] =
464 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
465 asyncResp->res.jsonValue["@odata.id"] =
466 "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries";
467 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
468 asyncResp->res.jsonValue["Description"] =
469 "Collection of System Event Log Entries";
470 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
471 logEntryArray = nlohmann::json::array();
472
473 // Go through the journal and create a unique ID for each entry
474 sd_journal *journalTmp = nullptr;
475 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
476 if (ret < 0)
477 {
478 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
479 messages::internalError(asyncResp->res);
480 return;
481 }
482 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
483 journalTmp, sd_journal_close);
484 journalTmp = nullptr;
485 uint64_t entryCount = 0;
486 SD_JOURNAL_FOREACH(journal.get())
487 {
488 // Look for only journal entries that contain a REDFISH_MESSAGE_ID
489 // field
490 boost::string_view messageID;
491 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID",
492 messageID);
493 if (ret < 0)
494 {
495 continue;
496 }
497
498 entryCount++;
499 // Handle paging using skip (number of entries to skip from the
500 // start) and top (number of entries to display)
501 if (entryCount <= skip || entryCount > skip + top)
502 {
503 continue;
504 }
505
506 std::string idStr;
507 if (!getUniqueEntryID(journal.get(), idStr))
508 {
509 continue;
510 }
511
512 logEntryArray.push_back({});
513 nlohmann::json &bmcLogEntry = logEntryArray.back();
514 if (fillEventLogEntryJson(name, idStr, messageID, journal.get(),
515 bmcLogEntry) != 0)
516 {
517 messages::internalError(asyncResp->res);
518 return;
519 }
520 }
521 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
522 if (skip + top < entryCount)
523 {
524 asyncResp->res.jsonValue["Members@odata.nextLink"] =
525 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
526 std::to_string(skip + top);
527 }
528 }
529};
530
531class EventLogEntry : public Node
532{
533 public:
534 EventLogEntry(CrowApp &app) :
535 Node(app,
536 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/",
537 std::string(), std::string())
538 {
539 entityPrivileges = {
540 {boost::beast::http::verb::get, {{"Login"}}},
541 {boost::beast::http::verb::head, {{"Login"}}},
542 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
543 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
544 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
545 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
546 }
547
548 private:
549 void doGet(crow::Response &res, const crow::Request &req,
550 const std::vector<std::string> &params) override
551 {
552 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
553 if (params.size() != 2)
554 {
555 messages::internalError(asyncResp->res);
556 return;
557 }
558 const std::string &name = params[0];
559 const std::string &entryID = params[1];
560 // Convert the unique ID back to a timestamp to find the entry
561 uint64_t ts = 0;
562 uint16_t index = 0;
563 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
564 {
565 return;
566 }
567
568 sd_journal *journalTmp = nullptr;
569 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
570 if (ret < 0)
571 {
572 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
573 messages::internalError(asyncResp->res);
574 return;
575 }
576 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
577 journalTmp, sd_journal_close);
578 journalTmp = nullptr;
579 // Go to the timestamp in the log and move to the entry at the index
580 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
581 for (int i = 0; i <= index; i++)
582 {
583 sd_journal_next(journal.get());
584 }
585 // Confirm that the entry ID matches what was requested
586 std::string idStr;
587 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
588 {
589 messages::resourceMissingAtURI(asyncResp->res, entryID);
590 return;
591 }
592
593 // only use journal entries that contain a REDFISH_MESSAGE_ID
594 // field
595 boost::string_view messageID;
596 ret =
597 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
598 if (ret < 0)
599 {
600 messages::resourceNotFound(asyncResp->res, "LogEntry", name);
601 return;
602 }
603
604 if (fillEventLogEntryJson(name, entryID, messageID, journal.get(),
605 asyncResp->res.jsonValue) != 0)
606 {
607 messages::internalError(asyncResp->res);
608 return;
609 }
610 }
611};
612
613class BMCLogServiceCollection : public Node
614{
615 public:
616 template <typename CrowApp>
617 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700618 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700619 {
620 // Collections use static ID for SubRoute to add to its parent, but only
621 // load dynamic data so the duplicate static members don't get displayed
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700622 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
Ed Tanous1da66f72018-07-27 16:13:37 -0700623 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700624 {boost::beast::http::verb::get, {{"Login"}}},
625 {boost::beast::http::verb::head, {{"Login"}}},
626 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
627 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
628 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
629 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700630 }
631
632 private:
633 /**
634 * Functions triggers appropriate requests on DBus
635 */
636 void doGet(crow::Response &res, const crow::Request &req,
637 const std::vector<std::string> &params) override
638 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700639 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700640 // Collections don't include the static data added by SubRoute because
641 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700642 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700643 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700644 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800645 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700646 asyncResp->res.jsonValue["@odata.id"] =
647 "/redfish/v1/Managers/bmc/LogServices";
648 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
649 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700650 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800651 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
652 logServiceArray = nlohmann::json::array();
653#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
654 logServiceArray.push_back(
655 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
656#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700657#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800658 logServiceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700659 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700660#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700661 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800662 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700663 }
664};
665
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800666class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700667{
668 public:
669 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800670 BMCJournalLogService(CrowApp &app) :
671 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700672 {
673 // Set the id for SubRoute
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800674 Node::json["@odata.id"] =
675 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700676 entityPrivileges = {
677 {boost::beast::http::verb::get, {{"Login"}}},
678 {boost::beast::http::verb::head, {{"Login"}}},
679 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
680 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
681 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
682 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
683 }
684
685 private:
686 void doGet(crow::Response &res, const crow::Request &req,
687 const std::vector<std::string> &params) override
688 {
689 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
690 // Copy over the static data to include the entries added by SubRoute
691 asyncResp->res.jsonValue = Node::json;
692 asyncResp->res.jsonValue["@odata.type"] =
693 "#LogService.v1_1_0.LogService";
694 asyncResp->res.jsonValue["@odata.context"] =
695 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800696 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
697 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
698 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700699 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
700 }
701};
702
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800703static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
704 sd_journal *journal,
705 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700706{
707 // Get the Log Entry contents
708 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700709
Jason M. Bills16428a12018-11-02 12:42:29 -0700710 boost::string_view msg;
711 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700712 if (ret < 0)
713 {
714 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
715 return 1;
716 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700717
718 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700719 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700720 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700721 if (ret < 0)
722 {
723 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
724 return 1;
725 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700726
727 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700728 std::string entryTimeStr;
729 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700730 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700731 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700732 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700733
734 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800735 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700736 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
737 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800738 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
739 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700740 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800741 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700742 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700743 {"EntryType", "Oem"},
744 {"Severity",
745 severity <= 2 ? "Critical"
746 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
747 {"OemRecordFormat", "Intel BMC Journal Entry"},
748 {"Created", std::move(entryTimeStr)}};
749 return 0;
750}
751
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800752class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700753{
754 public:
755 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800756 BMCJournalLogEntryCollection(CrowApp &app) :
757 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700758 {
759 // Collections use static ID for SubRoute to add to its parent, but only
760 // load dynamic data so the duplicate static members don't get displayed
761 Node::json["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800762 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700763 entityPrivileges = {
764 {boost::beast::http::verb::get, {{"Login"}}},
765 {boost::beast::http::verb::head, {{"Login"}}},
766 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
767 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
768 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
769 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
770 }
771
772 private:
773 void doGet(crow::Response &res, const crow::Request &req,
774 const std::vector<std::string> &params) override
775 {
776 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700777 static constexpr const long maxEntriesPerPage = 1000;
778 long skip = 0;
779 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700780 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700781 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700782 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700783 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700784 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700785 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700786 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700787 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700788 // Collections don't include the static data added by SubRoute because
789 // it has a duplicate entry for members
790 asyncResp->res.jsonValue["@odata.type"] =
791 "#LogEntryCollection.LogEntryCollection";
792 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800793 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700794 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800795 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700796 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
797 asyncResp->res.jsonValue["Description"] =
798 "Collection of BMC Journal Entries";
799 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
800 logEntryArray = nlohmann::json::array();
801
802 // Go through the journal and use the timestamp to create a unique ID
803 // for each entry
804 sd_journal *journalTmp = nullptr;
805 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
806 if (ret < 0)
807 {
808 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700809 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700810 return;
811 }
812 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
813 journalTmp, sd_journal_close);
814 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700815 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700816 SD_JOURNAL_FOREACH(journal.get())
817 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700818 entryCount++;
819 // Handle paging using skip (number of entries to skip from the
820 // start) and top (number of entries to display)
821 if (entryCount <= skip || entryCount > skip + top)
822 {
823 continue;
824 }
825
Jason M. Bills16428a12018-11-02 12:42:29 -0700826 std::string idStr;
827 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700828 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700829 continue;
830 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700831
Jason M. Billse1f26342018-07-18 12:12:00 -0700832 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800833 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
834 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
835 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700836 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700837 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700838 return;
839 }
840 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700841 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
842 if (skip + top < entryCount)
843 {
844 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800845 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700846 std::to_string(skip + top);
847 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700848 }
849};
850
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800851class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700852{
853 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800854 BMCJournalLogEntry(CrowApp &app) :
855 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700856 std::string())
857 {
858 entityPrivileges = {
859 {boost::beast::http::verb::get, {{"Login"}}},
860 {boost::beast::http::verb::head, {{"Login"}}},
861 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
862 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
863 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
864 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
865 }
866
867 private:
868 void doGet(crow::Response &res, const crow::Request &req,
869 const std::vector<std::string> &params) override
870 {
871 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
872 if (params.size() != 1)
873 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700874 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700875 return;
876 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700877 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700878 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700879 uint64_t ts = 0;
880 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700881 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700882 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700883 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700884 }
885
886 sd_journal *journalTmp = nullptr;
887 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
888 if (ret < 0)
889 {
890 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700891 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700892 return;
893 }
894 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
895 journalTmp, sd_journal_close);
896 journalTmp = nullptr;
897 // Go to the timestamp in the log and move to the entry at the index
898 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
899 for (int i = 0; i <= index; i++)
900 {
901 sd_journal_next(journal.get());
902 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800903 // Confirm that the entry ID matches what was requested
904 std::string idStr;
905 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
906 {
907 messages::resourceMissingAtURI(asyncResp->res, entryID);
908 return;
909 }
910
911 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
912 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700913 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700914 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700915 return;
916 }
917 }
918};
919
920class CPULogService : public Node
921{
922 public:
923 template <typename CrowApp>
924 CPULogService(CrowApp &app) :
925 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700926 {
927 // Set the id for SubRoute
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700928 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Ed Tanous1da66f72018-07-27 16:13:37 -0700929 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700930 {boost::beast::http::verb::get, {{"Login"}}},
931 {boost::beast::http::verb::head, {{"Login"}}},
932 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
933 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
934 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
935 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700936 }
937
938 private:
939 /**
940 * Functions triggers appropriate requests on DBus
941 */
942 void doGet(crow::Response &res, const crow::Request &req,
943 const std::vector<std::string> &params) override
944 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700945 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700946 // Copy over the static data to include the entries added by SubRoute
Jason M. Billse1f26342018-07-18 12:12:00 -0700947 asyncResp->res.jsonValue = Node::json;
948 asyncResp->res.jsonValue["@odata.type"] =
949 "#LogService.v1_1_0.LogService";
950 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800951 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700952 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
953 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
954 asyncResp->res.jsonValue["Id"] = "CPU Log";
955 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
956 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
957 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700958 {"Oem",
959 {{"#CpuLog.Immediate",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800960 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/"
961 "Actions/Oem/CpuLog.Immediate"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700962
963#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700964 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700965 {"#CpuLog.SendRawPeci",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800966 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/"
967 "Oem/CpuLog.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700968#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700969 }
970};
971
Jason M. Billse1f26342018-07-18 12:12:00 -0700972class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700973{
974 public:
975 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700976 CPULogEntryCollection(CrowApp &app) :
977 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700978 {
979 // Collections use static ID for SubRoute to add to its parent, but only
980 // load dynamic data so the duplicate static members don't get displayed
981 Node::json["@odata.id"] =
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700982 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
Ed Tanous1da66f72018-07-27 16:13:37 -0700983 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700984 {boost::beast::http::verb::get, {{"Login"}}},
985 {boost::beast::http::verb::head, {{"Login"}}},
986 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
987 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
988 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
989 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700990 }
991
992 private:
993 /**
994 * Functions triggers appropriate requests on DBus
995 */
996 void doGet(crow::Response &res, const crow::Request &req,
997 const std::vector<std::string> &params) override
998 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700999 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001000 // Collections don't include the static data added by SubRoute because
1001 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001002 auto getLogEntriesCallback = [asyncResp](
1003 const boost::system::error_code ec,
1004 const std::vector<std::string> &resp) {
1005 if (ec)
1006 {
1007 if (ec.value() !=
1008 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -07001009 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001010 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1011 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001012 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001013 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001014 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001015 }
1016 asyncResp->res.jsonValue["@odata.type"] =
1017 "#LogEntryCollection.LogEntryCollection";
1018 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001019 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001020 asyncResp->res.jsonValue["@odata.id"] =
1021 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1022 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1023 asyncResp->res.jsonValue["Description"] =
1024 "Collection of CPU Log Entries";
1025 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1026 logEntryArray = nlohmann::json::array();
1027 for (const std::string &objpath : resp)
1028 {
1029 // Don't list the immediate log
1030 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001031 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001032 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001033 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001034 std::size_t lastPos = objpath.rfind("/");
1035 if (lastPos != std::string::npos)
1036 {
1037 logEntryArray.push_back(
1038 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
1039 "CpuLog/Entries/" +
1040 objpath.substr(lastPos + 1)}});
1041 }
1042 }
1043 asyncResp->res.jsonValue["Members@odata.count"] =
1044 logEntryArray.size();
1045 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001046 crow::connections::systemBus->async_method_call(
1047 std::move(getLogEntriesCallback),
1048 "xyz.openbmc_project.ObjectMapper",
1049 "/xyz/openbmc_project/object_mapper",
1050 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001051 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001052 }
1053};
1054
1055std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1056{
1057 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
1058 if (metaIt != cpuLog.end())
1059 {
1060 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
1061 if (tsIt != metaIt->end())
1062 {
1063 const std::string *logTime = tsIt->get_ptr<const std::string *>();
1064 if (logTime != nullptr)
1065 {
1066 return *logTime;
1067 }
1068 }
1069 }
1070 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1071
1072 return std::string();
1073}
1074
Jason M. Billse1f26342018-07-18 12:12:00 -07001075class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001076{
1077 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001078 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001079 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001080 std::string())
1081 {
1082 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001083 {boost::beast::http::verb::get, {{"Login"}}},
1084 {boost::beast::http::verb::head, {{"Login"}}},
1085 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1086 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1087 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1088 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001089 }
1090
1091 private:
1092 void doGet(crow::Response &res, const crow::Request &req,
1093 const std::vector<std::string> &params) override
1094 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001095 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001096 if (params.size() != 1)
1097 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001098 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001099 return;
1100 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001101 const uint8_t logId = std::atoi(params[0].c_str());
Jason M. Billse1f26342018-07-18 12:12:00 -07001102 auto getStoredLogCallback =
1103 [asyncResp,
1104 logId](const boost::system::error_code ec,
1105 const sdbusplus::message::variant<std::string> &resp) {
1106 if (ec)
1107 {
1108 BMCWEB_LOG_DEBUG << "failed to get log ec: "
1109 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001110 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001111 return;
1112 }
1113 const std::string *log =
1114 mapbox::getPtr<const std::string>(resp);
1115 if (log == nullptr)
1116 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001117 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001118 return;
1119 }
1120 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1121 if (j.is_discarded())
1122 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001123 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001124 return;
1125 }
1126 std::string t = getLogCreatedTime(j);
1127 asyncResp->res.jsonValue = {
1128 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1129 {"@odata.context",
1130 "/redfish/v1/$metadata#LogEntry.LogEntry"},
1131 {"@odata.id",
1132 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1133 std::to_string(logId)},
1134 {"Name", "CPU Debug Log"},
1135 {"Id", logId},
1136 {"EntryType", "Oem"},
1137 {"OemRecordFormat", "Intel CPU Log"},
1138 {"Oem", {{"Intel", std::move(j)}}},
1139 {"Created", std::move(t)}};
1140 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001141 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001142 std::move(getStoredLogCallback), cpuLogObject,
1143 cpuLogPath + std::string("/") + std::to_string(logId),
1144 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001145 }
1146};
1147
Jason M. Billse1f26342018-07-18 12:12:00 -07001148class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001149{
1150 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001151 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001152 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001153 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001154 {
1155 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001156 {boost::beast::http::verb::get, {{"Login"}}},
1157 {boost::beast::http::verb::head, {{"Login"}}},
1158 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1159 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1160 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1161 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001162 }
1163
1164 private:
1165 void doPost(crow::Response &res, const crow::Request &req,
1166 const std::vector<std::string> &params) override
1167 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001168 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001169 static std::unique_ptr<sdbusplus::bus::match::match>
1170 immediateLogMatcher;
1171
1172 // Only allow one Immediate Log request at a time
1173 if (immediateLogMatcher != nullptr)
1174 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001175 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001176 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001177 return;
1178 }
1179 // Make this static so it survives outside this method
1180 static boost::asio::deadline_timer timeout(*req.ioService);
1181
1182 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001183 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001184 immediateLogMatcher = nullptr;
1185 if (ec)
1186 {
1187 // operation_aborted is expected if timer is canceled before
1188 // completion.
1189 if (ec != boost::asio::error::operation_aborted)
1190 {
1191 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1192 }
1193 return;
1194 }
1195 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1196
Jason M. Billsf12894f2018-10-09 12:45:45 -07001197 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001198 });
1199
Jason M. Billse1f26342018-07-18 12:12:00 -07001200 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001201 sdbusplus::message::message &m) {
1202 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1203 boost::system::error_code ec;
1204 timeout.cancel(ec);
1205 if (ec)
1206 {
1207 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1208 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001209 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001210 boost::container::flat_map<
1211 std::string,
1212 boost::container::flat_map<
1213 std::string, sdbusplus::message::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001214 interfacesAdded;
1215 m.read(objPath, interfacesAdded);
Ed Tanous1da66f72018-07-27 16:13:37 -07001216 const std::string *log = mapbox::getPtr<const std::string>(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001217 interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001218 if (log == nullptr)
1219 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001220 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001221 // Careful with immediateLogMatcher. It is a unique_ptr to the
1222 // match object inside which this lambda is executing. Once it
1223 // is set to nullptr, the match object will be destroyed and the
1224 // lambda will lose its context, including res, so it needs to
1225 // be the last thing done.
1226 immediateLogMatcher = nullptr;
1227 return;
1228 }
1229 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1230 if (j.is_discarded())
1231 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001232 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001233 // Careful with immediateLogMatcher. It is a unique_ptr to the
1234 // match object inside which this lambda is executing. Once it
1235 // is set to nullptr, the match object will be destroyed and the
1236 // lambda will lose its context, including res, so it needs to
1237 // be the last thing done.
1238 immediateLogMatcher = nullptr;
1239 return;
1240 }
1241 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001242 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001243 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1244 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1245 {"Name", "CPU Debug Log"},
1246 {"EntryType", "Oem"},
1247 {"OemRecordFormat", "Intel CPU Log"},
1248 {"Oem", {{"Intel", std::move(j)}}},
1249 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001250 // Careful with immediateLogMatcher. It is a unique_ptr to the
1251 // match object inside which this lambda is executing. Once it is
1252 // set to nullptr, the match object will be destroyed and the lambda
1253 // will lose its context, including res, so it needs to be the last
1254 // thing done.
1255 immediateLogMatcher = nullptr;
1256 };
1257 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1258 *crow::connections::systemBus,
1259 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001260 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001261 std::move(immediateLogMatcherCallback));
1262
1263 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001264 [asyncResp](const boost::system::error_code ec,
1265 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001266 if (ec)
1267 {
1268 if (ec.value() ==
1269 boost::system::errc::operation_not_supported)
1270 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001271 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001272 }
1273 else
1274 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001275 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001276 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001277 boost::system::error_code timeoutec;
1278 timeout.cancel(timeoutec);
1279 if (timeoutec)
1280 {
1281 BMCWEB_LOG_ERROR << "error canceling timer "
1282 << timeoutec;
1283 }
1284 immediateLogMatcher = nullptr;
1285 return;
1286 }
1287 };
1288 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001289 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1290 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001291 }
1292};
1293
Jason M. Billse1f26342018-07-18 12:12:00 -07001294class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001295{
1296 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001297 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001298 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001299 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001300 {
1301 entityPrivileges = {
1302 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1303 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1304 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1305 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1306 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1307 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1308 }
1309
1310 private:
1311 void doPost(crow::Response &res, const crow::Request &req,
1312 const std::vector<std::string> &params) override
1313 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001314 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001315 uint8_t clientAddress = 0;
1316 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001317 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001318 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1319 "ReadLength", readLength, "PECICommand",
1320 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001321 {
Ed Tanousb1556422018-10-16 14:09:17 -07001322 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001323 }
Ed Tanousb1556422018-10-16 14:09:17 -07001324
Ed Tanous1da66f72018-07-27 16:13:37 -07001325 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001326 auto sendRawPECICallback =
1327 [asyncResp](const boost::system::error_code ec,
1328 const std::vector<uint8_t> &resp) {
1329 if (ec)
1330 {
1331 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1332 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001333 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001334 return;
1335 }
1336 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1337 {"PECIResponse", resp}};
1338 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001339 // Call the SendRawPECI command with the provided data
1340 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001341 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1342 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001343 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001344 }
1345};
1346
1347} // namespace redfish