blob: cf4af022310712dc60af163ce149ed8148c33233 [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 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700620 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700621 {boost::beast::http::verb::get, {{"Login"}}},
622 {boost::beast::http::verb::head, {{"Login"}}},
623 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
624 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
625 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
626 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700627 }
628
629 private:
630 /**
631 * Functions triggers appropriate requests on DBus
632 */
633 void doGet(crow::Response &res, const crow::Request &req,
634 const std::vector<std::string> &params) override
635 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700636 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700637 // Collections don't include the static data added by SubRoute because
638 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700639 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700640 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700641 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800642 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700643 asyncResp->res.jsonValue["@odata.id"] =
644 "/redfish/v1/Managers/bmc/LogServices";
645 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
646 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700647 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800648 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
649 logServiceArray = nlohmann::json::array();
650#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
651 logServiceArray.push_back(
652 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
653#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700654#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800655 logServiceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700656 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700657#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700658 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800659 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700660 }
661};
662
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800663class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700664{
665 public:
666 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800667 BMCJournalLogService(CrowApp &app) :
668 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700669 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700670 entityPrivileges = {
671 {boost::beast::http::verb::get, {{"Login"}}},
672 {boost::beast::http::verb::head, {{"Login"}}},
673 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
674 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
675 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
676 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
677 }
678
679 private:
680 void doGet(crow::Response &res, const crow::Request &req,
681 const std::vector<std::string> &params) override
682 {
683 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700684 asyncResp->res.jsonValue["@odata.type"] =
685 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800686 asyncResp->res.jsonValue["@odata.id"] =
687 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700688 asyncResp->res.jsonValue["@odata.context"] =
689 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800690 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
691 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
692 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700693 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
694 }
695};
696
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800697static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
698 sd_journal *journal,
699 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700700{
701 // Get the Log Entry contents
702 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700703
Jason M. Bills16428a12018-11-02 12:42:29 -0700704 boost::string_view msg;
705 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700706 if (ret < 0)
707 {
708 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
709 return 1;
710 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700711
712 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700713 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700714 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700715 if (ret < 0)
716 {
717 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
718 return 1;
719 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700720
721 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700722 std::string entryTimeStr;
723 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700724 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700725 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700726 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700727
728 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800729 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700730 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
731 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800732 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
733 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700734 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800735 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700736 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700737 {"EntryType", "Oem"},
738 {"Severity",
739 severity <= 2 ? "Critical"
740 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
741 {"OemRecordFormat", "Intel BMC Journal Entry"},
742 {"Created", std::move(entryTimeStr)}};
743 return 0;
744}
745
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800746class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700747{
748 public:
749 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800750 BMCJournalLogEntryCollection(CrowApp &app) :
751 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700752 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700753 entityPrivileges = {
754 {boost::beast::http::verb::get, {{"Login"}}},
755 {boost::beast::http::verb::head, {{"Login"}}},
756 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
757 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
758 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
759 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
760 }
761
762 private:
763 void doGet(crow::Response &res, const crow::Request &req,
764 const std::vector<std::string> &params) override
765 {
766 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700767 static constexpr const long maxEntriesPerPage = 1000;
768 long skip = 0;
769 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700770 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700771 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700772 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700773 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700774 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700775 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700776 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700777 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700778 // Collections don't include the static data added by SubRoute because
779 // it has a duplicate entry for members
780 asyncResp->res.jsonValue["@odata.type"] =
781 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -0800782 asyncResp->res.jsonValue["@odata.id"] =
783 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700784 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800785 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700786 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800787 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700788 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
789 asyncResp->res.jsonValue["Description"] =
790 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -0800791 asyncResp->res.jsonValue["@odata.id"] =
792 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700793 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
794 logEntryArray = nlohmann::json::array();
795
796 // Go through the journal and use the timestamp to create a unique ID
797 // for each entry
798 sd_journal *journalTmp = nullptr;
799 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
800 if (ret < 0)
801 {
802 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700803 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700804 return;
805 }
806 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
807 journalTmp, sd_journal_close);
808 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700809 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700810 SD_JOURNAL_FOREACH(journal.get())
811 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700812 entryCount++;
813 // Handle paging using skip (number of entries to skip from the
814 // start) and top (number of entries to display)
815 if (entryCount <= skip || entryCount > skip + top)
816 {
817 continue;
818 }
819
Jason M. Bills16428a12018-11-02 12:42:29 -0700820 std::string idStr;
821 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700822 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700823 continue;
824 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700825
Jason M. Billse1f26342018-07-18 12:12:00 -0700826 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800827 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
828 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
829 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700830 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700831 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700832 return;
833 }
834 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700835 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
836 if (skip + top < entryCount)
837 {
838 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800839 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700840 std::to_string(skip + top);
841 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700842 }
843};
844
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800845class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700846{
847 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800848 BMCJournalLogEntry(CrowApp &app) :
849 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700850 std::string())
851 {
852 entityPrivileges = {
853 {boost::beast::http::verb::get, {{"Login"}}},
854 {boost::beast::http::verb::head, {{"Login"}}},
855 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
856 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
857 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
858 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
859 }
860
861 private:
862 void doGet(crow::Response &res, const crow::Request &req,
863 const std::vector<std::string> &params) override
864 {
865 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
866 if (params.size() != 1)
867 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700868 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700869 return;
870 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700871 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700872 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700873 uint64_t ts = 0;
874 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700875 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700876 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700877 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700878 }
879
880 sd_journal *journalTmp = nullptr;
881 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
882 if (ret < 0)
883 {
884 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700885 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700886 return;
887 }
888 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
889 journalTmp, sd_journal_close);
890 journalTmp = nullptr;
891 // Go to the timestamp in the log and move to the entry at the index
892 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
893 for (int i = 0; i <= index; i++)
894 {
895 sd_journal_next(journal.get());
896 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800897 // Confirm that the entry ID matches what was requested
898 std::string idStr;
899 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
900 {
901 messages::resourceMissingAtURI(asyncResp->res, entryID);
902 return;
903 }
904
905 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
906 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700907 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700908 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700909 return;
910 }
911 }
912};
913
914class CPULogService : public Node
915{
916 public:
917 template <typename CrowApp>
918 CPULogService(CrowApp &app) :
919 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700920 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700921 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700922 {boost::beast::http::verb::get, {{"Login"}}},
923 {boost::beast::http::verb::head, {{"Login"}}},
924 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
925 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
926 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
927 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700928 }
929
930 private:
931 /**
932 * Functions triggers appropriate requests on DBus
933 */
934 void doGet(crow::Response &res, const crow::Request &req,
935 const std::vector<std::string> &params) override
936 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700937 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700938 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -0800939 asyncResp->res.jsonValue["@odata.id"] =
940 "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Jason M. Billse1f26342018-07-18 12:12:00 -0700941 asyncResp->res.jsonValue["@odata.type"] =
942 "#LogService.v1_1_0.LogService";
943 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800944 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700945 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
946 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
947 asyncResp->res.jsonValue["Id"] = "CPU Log";
948 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
949 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
950 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700951 {"Oem",
952 {{"#CpuLog.Immediate",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800953 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/"
954 "Actions/Oem/CpuLog.Immediate"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700955
956#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700957 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700958 {"#CpuLog.SendRawPeci",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800959 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/"
960 "Oem/CpuLog.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700961#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700962 }
963};
964
Jason M. Billse1f26342018-07-18 12:12:00 -0700965class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700966{
967 public:
968 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700969 CPULogEntryCollection(CrowApp &app) :
970 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700971 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700972 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700973 {boost::beast::http::verb::get, {{"Login"}}},
974 {boost::beast::http::verb::head, {{"Login"}}},
975 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
976 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
977 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
978 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700979 }
980
981 private:
982 /**
983 * Functions triggers appropriate requests on DBus
984 */
985 void doGet(crow::Response &res, const crow::Request &req,
986 const std::vector<std::string> &params) override
987 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700988 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700989 // Collections don't include the static data added by SubRoute because
990 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700991 auto getLogEntriesCallback = [asyncResp](
992 const boost::system::error_code ec,
993 const std::vector<std::string> &resp) {
994 if (ec)
995 {
996 if (ec.value() !=
997 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -0700998 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700999 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1000 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001001 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001002 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001003 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001004 }
1005 asyncResp->res.jsonValue["@odata.type"] =
1006 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001007 asyncResp->res.jsonValue["@odata.id"] =
1008 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001009 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001010 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001011 asyncResp->res.jsonValue["@odata.id"] =
1012 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1013 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1014 asyncResp->res.jsonValue["Description"] =
1015 "Collection of CPU Log Entries";
1016 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1017 logEntryArray = nlohmann::json::array();
1018 for (const std::string &objpath : resp)
1019 {
1020 // Don't list the immediate log
1021 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001022 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001023 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001024 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001025 std::size_t lastPos = objpath.rfind("/");
1026 if (lastPos != std::string::npos)
1027 {
1028 logEntryArray.push_back(
1029 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
1030 "CpuLog/Entries/" +
1031 objpath.substr(lastPos + 1)}});
1032 }
1033 }
1034 asyncResp->res.jsonValue["Members@odata.count"] =
1035 logEntryArray.size();
1036 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001037 crow::connections::systemBus->async_method_call(
1038 std::move(getLogEntriesCallback),
1039 "xyz.openbmc_project.ObjectMapper",
1040 "/xyz/openbmc_project/object_mapper",
1041 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001042 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001043 }
1044};
1045
1046std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1047{
1048 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
1049 if (metaIt != cpuLog.end())
1050 {
1051 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
1052 if (tsIt != metaIt->end())
1053 {
1054 const std::string *logTime = tsIt->get_ptr<const std::string *>();
1055 if (logTime != nullptr)
1056 {
1057 return *logTime;
1058 }
1059 }
1060 }
1061 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1062
1063 return std::string();
1064}
1065
Jason M. Billse1f26342018-07-18 12:12:00 -07001066class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001067{
1068 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001069 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001070 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001071 std::string())
1072 {
1073 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001074 {boost::beast::http::verb::get, {{"Login"}}},
1075 {boost::beast::http::verb::head, {{"Login"}}},
1076 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1077 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1078 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1079 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001080 }
1081
1082 private:
1083 void doGet(crow::Response &res, const crow::Request &req,
1084 const std::vector<std::string> &params) override
1085 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001086 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001087 if (params.size() != 1)
1088 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001089 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001090 return;
1091 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001092 const uint8_t logId = std::atoi(params[0].c_str());
Jason M. Billse1f26342018-07-18 12:12:00 -07001093 auto getStoredLogCallback =
1094 [asyncResp,
1095 logId](const boost::system::error_code ec,
1096 const sdbusplus::message::variant<std::string> &resp) {
1097 if (ec)
1098 {
1099 BMCWEB_LOG_DEBUG << "failed to get log ec: "
1100 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001101 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001102 return;
1103 }
1104 const std::string *log =
1105 mapbox::getPtr<const std::string>(resp);
1106 if (log == nullptr)
1107 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001108 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001109 return;
1110 }
1111 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1112 if (j.is_discarded())
1113 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001114 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001115 return;
1116 }
1117 std::string t = getLogCreatedTime(j);
1118 asyncResp->res.jsonValue = {
1119 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1120 {"@odata.context",
1121 "/redfish/v1/$metadata#LogEntry.LogEntry"},
1122 {"@odata.id",
1123 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1124 std::to_string(logId)},
1125 {"Name", "CPU Debug Log"},
1126 {"Id", logId},
1127 {"EntryType", "Oem"},
1128 {"OemRecordFormat", "Intel CPU Log"},
1129 {"Oem", {{"Intel", std::move(j)}}},
1130 {"Created", std::move(t)}};
1131 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001132 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001133 std::move(getStoredLogCallback), cpuLogObject,
1134 cpuLogPath + std::string("/") + std::to_string(logId),
1135 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001136 }
1137};
1138
Jason M. Billse1f26342018-07-18 12:12:00 -07001139class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001140{
1141 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001142 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001143 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001144 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001145 {
1146 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001147 {boost::beast::http::verb::get, {{"Login"}}},
1148 {boost::beast::http::verb::head, {{"Login"}}},
1149 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1150 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1151 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1152 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001153 }
1154
1155 private:
1156 void doPost(crow::Response &res, const crow::Request &req,
1157 const std::vector<std::string> &params) override
1158 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001159 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001160 static std::unique_ptr<sdbusplus::bus::match::match>
1161 immediateLogMatcher;
1162
1163 // Only allow one Immediate Log request at a time
1164 if (immediateLogMatcher != nullptr)
1165 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001166 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001167 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001168 return;
1169 }
1170 // Make this static so it survives outside this method
1171 static boost::asio::deadline_timer timeout(*req.ioService);
1172
1173 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001174 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001175 immediateLogMatcher = nullptr;
1176 if (ec)
1177 {
1178 // operation_aborted is expected if timer is canceled before
1179 // completion.
1180 if (ec != boost::asio::error::operation_aborted)
1181 {
1182 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1183 }
1184 return;
1185 }
1186 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1187
Jason M. Billsf12894f2018-10-09 12:45:45 -07001188 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001189 });
1190
Jason M. Billse1f26342018-07-18 12:12:00 -07001191 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001192 sdbusplus::message::message &m) {
1193 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1194 boost::system::error_code ec;
1195 timeout.cancel(ec);
1196 if (ec)
1197 {
1198 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1199 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001200 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001201 boost::container::flat_map<
1202 std::string,
1203 boost::container::flat_map<
1204 std::string, sdbusplus::message::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001205 interfacesAdded;
1206 m.read(objPath, interfacesAdded);
Ed Tanous1da66f72018-07-27 16:13:37 -07001207 const std::string *log = mapbox::getPtr<const std::string>(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001208 interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001209 if (log == nullptr)
1210 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001211 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001212 // Careful with immediateLogMatcher. It is a unique_ptr to the
1213 // match object inside which this lambda is executing. Once it
1214 // is set to nullptr, the match object will be destroyed and the
1215 // lambda will lose its context, including res, so it needs to
1216 // be the last thing done.
1217 immediateLogMatcher = nullptr;
1218 return;
1219 }
1220 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1221 if (j.is_discarded())
1222 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001223 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001224 // Careful with immediateLogMatcher. It is a unique_ptr to the
1225 // match object inside which this lambda is executing. Once it
1226 // is set to nullptr, the match object will be destroyed and the
1227 // lambda will lose its context, including res, so it needs to
1228 // be the last thing done.
1229 immediateLogMatcher = nullptr;
1230 return;
1231 }
1232 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001233 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001234 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1235 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1236 {"Name", "CPU Debug Log"},
1237 {"EntryType", "Oem"},
1238 {"OemRecordFormat", "Intel CPU Log"},
1239 {"Oem", {{"Intel", std::move(j)}}},
1240 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001241 // Careful with immediateLogMatcher. It is a unique_ptr to the
1242 // match object inside which this lambda is executing. Once it is
1243 // set to nullptr, the match object will be destroyed and the lambda
1244 // will lose its context, including res, so it needs to be the last
1245 // thing done.
1246 immediateLogMatcher = nullptr;
1247 };
1248 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1249 *crow::connections::systemBus,
1250 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001251 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001252 std::move(immediateLogMatcherCallback));
1253
1254 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001255 [asyncResp](const boost::system::error_code ec,
1256 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001257 if (ec)
1258 {
1259 if (ec.value() ==
1260 boost::system::errc::operation_not_supported)
1261 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001262 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001263 }
1264 else
1265 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001266 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001267 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001268 boost::system::error_code timeoutec;
1269 timeout.cancel(timeoutec);
1270 if (timeoutec)
1271 {
1272 BMCWEB_LOG_ERROR << "error canceling timer "
1273 << timeoutec;
1274 }
1275 immediateLogMatcher = nullptr;
1276 return;
1277 }
1278 };
1279 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001280 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1281 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001282 }
1283};
1284
Jason M. Billse1f26342018-07-18 12:12:00 -07001285class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001286{
1287 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001288 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001289 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001290 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001291 {
1292 entityPrivileges = {
1293 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1294 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1295 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1296 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1297 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1298 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1299 }
1300
1301 private:
1302 void doPost(crow::Response &res, const crow::Request &req,
1303 const std::vector<std::string> &params) override
1304 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001305 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001306 uint8_t clientAddress = 0;
1307 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001308 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001309 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1310 "ReadLength", readLength, "PECICommand",
1311 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001312 {
Ed Tanousb1556422018-10-16 14:09:17 -07001313 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001314 }
Ed Tanousb1556422018-10-16 14:09:17 -07001315
Ed Tanous1da66f72018-07-27 16:13:37 -07001316 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001317 auto sendRawPECICallback =
1318 [asyncResp](const boost::system::error_code ec,
1319 const std::vector<uint8_t> &resp) {
1320 if (ec)
1321 {
1322 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1323 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001324 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001325 return;
1326 }
1327 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1328 {"PECIResponse", resp}};
1329 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001330 // Call the SendRawPECI command with the provided data
1331 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001332 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1333 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001334 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001335 }
1336};
1337
1338} // namespace redfish