blob: ce5b97f1d0dca1b8d912b1e84e5af2fccee80ac0 [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
James Feistf6150402019-01-08 10:36:20 -080018#include "filesystem.hpp"
Ed Tanous1da66f72018-07-27 16:13:37 -070019#include "node.hpp"
20
Jason M. Billse1f26342018-07-18 12:12:00 -070021#include <systemd/sd-journal.h>
22
Ed Tanous1da66f72018-07-27 16:13:37 -070023#include <boost/container/flat_map.hpp>
Jason M. Billse1f26342018-07-18 12:12:00 -070024#include <boost/utility/string_view.hpp>
Ed Tanousabf2add2019-01-22 16:40:12 -080025#include <variant>
Ed Tanous1da66f72018-07-27 16:13:37 -070026
27namespace redfish
28{
29
Ed Tanous4ed77cd2018-10-15 08:08:07 -070030constexpr char const *cpuLogObject = "com.intel.CpuDebugLog";
31constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog";
32constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate";
33constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
34constexpr char const *cpuLogImmediateInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070035 "com.intel.CpuDebugLog.Immediate";
Jason M. Billse1f26342018-07-18 12:12:00 -070036constexpr char const *cpuLogRawPECIInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070037 "com.intel.CpuDebugLog.SendRawPeci";
38
James Feistf6150402019-01-08 10:36:20 -080039namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070040
Jason M. Bills16428a12018-11-02 12:42:29 -070041static int getJournalMetadata(sd_journal *journal,
42 const boost::string_view &field,
43 boost::string_view &contents)
44{
45 const char *data = nullptr;
46 size_t length = 0;
47 int ret = 0;
48 // Get the metadata from the requested field of the journal entry
49 ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
50 &length);
51 if (ret < 0)
52 {
53 return ret;
54 }
55 contents = boost::string_view(data, length);
56 // Only use the content after the "=" character.
57 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
58 return ret;
59}
60
61static int getJournalMetadata(sd_journal *journal,
62 const boost::string_view &field, const int &base,
63 int &contents)
64{
65 int ret = 0;
66 boost::string_view metadata;
67 // Get the metadata from the requested field of the journal entry
68 ret = getJournalMetadata(journal, field, metadata);
69 if (ret < 0)
70 {
71 return ret;
72 }
73 contents = strtol(metadata.data(), nullptr, base);
74 return ret;
75}
76
77static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
78{
79 int ret = 0;
80 uint64_t timestamp = 0;
81 ret = sd_journal_get_realtime_usec(journal, &timestamp);
82 if (ret < 0)
83 {
84 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
85 << strerror(-ret);
86 return false;
87 }
88 time_t t =
89 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
90 struct tm *loctime = localtime(&t);
91 char entryTime[64] = {};
92 if (NULL != loctime)
93 {
94 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
95 }
96 // Insert the ':' into the timezone
97 boost::string_view t1(entryTime);
98 boost::string_view t2(entryTime);
99 if (t1.size() > 2 && t2.size() > 2)
100 {
101 t1.remove_suffix(2);
102 t2.remove_prefix(t2.size() - 2);
103 }
104 entryTimestamp = t1.to_string() + ":" + t2.to_string();
105 return true;
106}
107
108static bool getSkipParam(crow::Response &res, const crow::Request &req,
109 long &skip)
110{
111 char *skipParam = req.urlParams.get("$skip");
112 if (skipParam != nullptr)
113 {
114 char *ptr = nullptr;
115 skip = std::strtol(skipParam, &ptr, 10);
116 if (*skipParam == '\0' || *ptr != '\0')
117 {
118
119 messages::queryParameterValueTypeError(res, std::string(skipParam),
120 "$skip");
121 return false;
122 }
123 if (skip < 0)
124 {
125
126 messages::queryParameterOutOfRange(res, std::to_string(skip),
127 "$skip", "greater than 0");
128 return false;
129 }
130 }
131 return true;
132}
133
134static constexpr const long maxEntriesPerPage = 1000;
135static bool getTopParam(crow::Response &res, const crow::Request &req,
136 long &top)
137{
138 char *topParam = req.urlParams.get("$top");
139 if (topParam != nullptr)
140 {
141 char *ptr = nullptr;
142 top = std::strtol(topParam, &ptr, 10);
143 if (*topParam == '\0' || *ptr != '\0')
144 {
145 messages::queryParameterValueTypeError(res, std::string(topParam),
146 "$top");
147 return false;
148 }
149 if (top < 1 || top > maxEntriesPerPage)
150 {
151
152 messages::queryParameterOutOfRange(
153 res, std::to_string(top), "$top",
154 "1-" + std::to_string(maxEntriesPerPage));
155 return false;
156 }
157 }
158 return true;
159}
160
161static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
162{
163 int ret = 0;
164 static uint64_t prevTs = 0;
165 static int index = 0;
166 // Get the entry timestamp
167 uint64_t curTs = 0;
168 ret = sd_journal_get_realtime_usec(journal, &curTs);
169 if (ret < 0)
170 {
171 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
172 << strerror(-ret);
173 return false;
174 }
175 // If the timestamp isn't unique, increment the index
176 if (curTs == prevTs)
177 {
178 index++;
179 }
180 else
181 {
182 // Otherwise, reset it
183 index = 0;
184 }
185 // Save the timestamp
186 prevTs = curTs;
187
188 entryID = std::to_string(curTs);
189 if (index > 0)
190 {
191 entryID += "_" + std::to_string(index);
192 }
193 return true;
194}
195
196static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
197 uint64_t &timestamp, uint16_t &index)
198{
199 if (entryID.empty())
200 {
201 return false;
202 }
203 // Convert the unique ID back to a timestamp to find the entry
204 boost::string_view tsStr(entryID);
205
206 auto underscorePos = tsStr.find("_");
207 if (underscorePos != tsStr.npos)
208 {
209 // Timestamp has an index
210 tsStr.remove_suffix(tsStr.size() - underscorePos);
211 boost::string_view indexStr(entryID);
212 indexStr.remove_prefix(underscorePos + 1);
213 std::size_t pos;
214 try
215 {
216 index = std::stoul(indexStr.to_string(), &pos);
217 }
218 catch (std::invalid_argument)
219 {
220 messages::resourceMissingAtURI(res, entryID);
221 return false;
222 }
223 catch (std::out_of_range)
224 {
225 messages::resourceMissingAtURI(res, entryID);
226 return false;
227 }
228 if (pos != indexStr.size())
229 {
230 messages::resourceMissingAtURI(res, entryID);
231 return false;
232 }
233 }
234 // Timestamp has no index
235 std::size_t pos;
236 try
237 {
238 timestamp = std::stoull(tsStr.to_string(), &pos);
239 }
240 catch (std::invalid_argument)
241 {
242 messages::resourceMissingAtURI(res, entryID);
243 return false;
244 }
245 catch (std::out_of_range)
246 {
247 messages::resourceMissingAtURI(res, entryID);
248 return false;
249 }
250 if (pos != tsStr.size())
251 {
252 messages::resourceMissingAtURI(res, entryID);
253 return false;
254 }
255 return true;
256}
257
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800258class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700259{
260 public:
261 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800262 SystemLogServiceCollection(CrowApp &app) :
263 Node(app, "/redfish/v1/Systems/<str>/LogServices/", std::string())
264 {
265 entityPrivileges = {
266 {boost::beast::http::verb::get, {{"Login"}}},
267 {boost::beast::http::verb::head, {{"Login"}}},
268 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
269 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
270 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
271 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
272 }
273
274 private:
275 /**
276 * Functions triggers appropriate requests on DBus
277 */
278 void doGet(crow::Response &res, const crow::Request &req,
279 const std::vector<std::string> &params) override
280 {
281 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
282 const std::string &name = params[0];
283 // Collections don't include the static data added by SubRoute because
284 // it has a duplicate entry for members
285 asyncResp->res.jsonValue["@odata.type"] =
286 "#LogServiceCollection.LogServiceCollection";
287 asyncResp->res.jsonValue["@odata.context"] =
288 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
289 asyncResp->res.jsonValue["@odata.id"] =
290 "/redfish/v1/Systems/" + name + "/LogServices";
291 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
292 asyncResp->res.jsonValue["Description"] =
293 "Collection of LogServices for this Computer System";
294 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
295 logServiceArray = nlohmann::json::array();
296 logServiceArray.push_back({{"@odata.id", "/redfish/v1/Systems/" + name +
297 "/LogServices/EventLog"}});
298 asyncResp->res.jsonValue["Members@odata.count"] =
299 logServiceArray.size();
300 }
301};
302
303class EventLogService : public Node
304{
305 public:
306 template <typename CrowApp>
307 EventLogService(CrowApp &app) :
308 Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/",
309 std::string())
310 {
311 entityPrivileges = {
312 {boost::beast::http::verb::get, {{"Login"}}},
313 {boost::beast::http::verb::head, {{"Login"}}},
314 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
316 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
317 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
318 }
319
320 private:
321 void doGet(crow::Response &res, const crow::Request &req,
322 const std::vector<std::string> &params) override
323 {
324 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
325
326 const std::string &name = params[0];
327 asyncResp->res.jsonValue["@odata.id"] =
328 "/redfish/v1/Systems/" + name + "/LogServices/EventLog";
329 asyncResp->res.jsonValue["@odata.type"] =
330 "#LogService.v1_1_0.LogService";
331 asyncResp->res.jsonValue["@odata.context"] =
332 "/redfish/v1/$metadata#LogService.LogService";
333 asyncResp->res.jsonValue["Name"] = "Event Log Service";
334 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
335 asyncResp->res.jsonValue["Id"] = "Event Log";
336 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
337 asyncResp->res.jsonValue["Entries"] = {
338 {"@odata.id",
339 "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries"}};
340 }
341};
342
343static int fillEventLogEntryJson(const std::string &systemName,
344 const std::string &bmcLogEntryID,
345 const boost::string_view &messageID,
346 sd_journal *journal,
347 nlohmann::json &bmcLogEntryJson)
348{
349 // Get the Log Entry contents
350 int ret = 0;
351
352 boost::string_view msg;
353 ret = getJournalMetadata(journal, "MESSAGE", msg);
354 if (ret < 0)
355 {
356 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
357 return 1;
358 }
359
360 // Get the severity from the PRIORITY field
361 int severity = 8; // Default to an invalid priority
362 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
363 if (ret < 0)
364 {
365 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
366 return 1;
367 }
368
369 // Get the MessageArgs from the journal entry by finding all of the
370 // REDFISH_MESSAGE_ARG_x fields
371 const void *data;
372 size_t length;
373 std::vector<std::string> messageArgs;
374 SD_JOURNAL_FOREACH_DATA(journal, data, length)
375 {
376 boost::string_view field(static_cast<const char *>(data), length);
377 if (field.starts_with("REDFISH_MESSAGE_ARG_"))
378 {
379 // Get the Arg number from the field name
380 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1);
381 if (field.empty())
382 {
383 continue;
384 }
385 int argNum = std::strtoul(field.data(), nullptr, 10);
386 if (argNum == 0)
387 {
388 continue;
389 }
390 // Get the Arg value after the "=" character.
391 field.remove_prefix(std::min(field.find("=") + 1, field.size()));
392 // Make sure we have enough space in messageArgs
393 if (argNum > messageArgs.size())
394 {
395 messageArgs.resize(argNum);
396 }
397 messageArgs[argNum - 1] = field.to_string();
398 }
399 }
400
401 // Get the Created time from the timestamp
402 std::string entryTimeStr;
403 if (!getEntryTimestamp(journal, entryTimeStr))
404 {
405 return 1;
406 }
407
408 // Fill in the log entry with the gathered data
409 bmcLogEntryJson = {
410 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
411 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
412 {"@odata.id", "/redfish/v1/Systems/" + systemName +
413 "/LogServices/EventLog/Entries/" + bmcLogEntryID},
414 {"Name", "System Event Log Entry"},
415 {"Id", bmcLogEntryID},
416 {"Message", msg},
417 {"MessageId", messageID},
418 {"MessageArgs", std::move(messageArgs)},
419 {"EntryType", "Event"},
420 {"Severity",
421 severity <= 2 ? "Critical"
422 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
423 {"Created", std::move(entryTimeStr)}};
424 return 0;
425}
426
427class EventLogEntryCollection : public Node
428{
429 public:
430 template <typename CrowApp>
431 EventLogEntryCollection(CrowApp &app) :
432 Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/",
433 std::string())
434 {
435 entityPrivileges = {
436 {boost::beast::http::verb::get, {{"Login"}}},
437 {boost::beast::http::verb::head, {{"Login"}}},
438 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
439 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
440 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
441 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
442 }
443
444 private:
445 void doGet(crow::Response &res, const crow::Request &req,
446 const std::vector<std::string> &params) override
447 {
448 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
449 long skip = 0;
450 long top = maxEntriesPerPage; // Show max entries by default
451 if (!getSkipParam(asyncResp->res, req, skip))
452 {
453 return;
454 }
455 if (!getTopParam(asyncResp->res, req, top))
456 {
457 return;
458 }
459 const std::string &name = params[0];
460 // Collections don't include the static data added by SubRoute because
461 // it has a duplicate entry for members
462 asyncResp->res.jsonValue["@odata.type"] =
463 "#LogEntryCollection.LogEntryCollection";
464 asyncResp->res.jsonValue["@odata.context"] =
465 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
466 asyncResp->res.jsonValue["@odata.id"] =
467 "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries";
468 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
469 asyncResp->res.jsonValue["Description"] =
470 "Collection of System Event Log Entries";
471 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
472 logEntryArray = nlohmann::json::array();
473
474 // Go through the journal and create a unique ID for each entry
475 sd_journal *journalTmp = nullptr;
476 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
477 if (ret < 0)
478 {
479 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
480 messages::internalError(asyncResp->res);
481 return;
482 }
483 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
484 journalTmp, sd_journal_close);
485 journalTmp = nullptr;
486 uint64_t entryCount = 0;
487 SD_JOURNAL_FOREACH(journal.get())
488 {
489 // Look for only journal entries that contain a REDFISH_MESSAGE_ID
490 // field
491 boost::string_view messageID;
492 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID",
493 messageID);
494 if (ret < 0)
495 {
496 continue;
497 }
498
499 entryCount++;
500 // Handle paging using skip (number of entries to skip from the
501 // start) and top (number of entries to display)
502 if (entryCount <= skip || entryCount > skip + top)
503 {
504 continue;
505 }
506
507 std::string idStr;
508 if (!getUniqueEntryID(journal.get(), idStr))
509 {
510 continue;
511 }
512
513 logEntryArray.push_back({});
514 nlohmann::json &bmcLogEntry = logEntryArray.back();
515 if (fillEventLogEntryJson(name, idStr, messageID, journal.get(),
516 bmcLogEntry) != 0)
517 {
518 messages::internalError(asyncResp->res);
519 return;
520 }
521 }
522 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
523 if (skip + top < entryCount)
524 {
525 asyncResp->res.jsonValue["Members@odata.nextLink"] =
526 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
527 std::to_string(skip + top);
528 }
529 }
530};
531
532class EventLogEntry : public Node
533{
534 public:
535 EventLogEntry(CrowApp &app) :
536 Node(app,
537 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/",
538 std::string(), std::string())
539 {
540 entityPrivileges = {
541 {boost::beast::http::verb::get, {{"Login"}}},
542 {boost::beast::http::verb::head, {{"Login"}}},
543 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
544 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
545 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
546 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
547 }
548
549 private:
550 void doGet(crow::Response &res, const crow::Request &req,
551 const std::vector<std::string> &params) override
552 {
553 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
554 if (params.size() != 2)
555 {
556 messages::internalError(asyncResp->res);
557 return;
558 }
559 const std::string &name = params[0];
560 const std::string &entryID = params[1];
561 // Convert the unique ID back to a timestamp to find the entry
562 uint64_t ts = 0;
563 uint16_t index = 0;
564 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
565 {
566 return;
567 }
568
569 sd_journal *journalTmp = nullptr;
570 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
571 if (ret < 0)
572 {
573 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
574 messages::internalError(asyncResp->res);
575 return;
576 }
577 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
578 journalTmp, sd_journal_close);
579 journalTmp = nullptr;
580 // Go to the timestamp in the log and move to the entry at the index
581 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
582 for (int i = 0; i <= index; i++)
583 {
584 sd_journal_next(journal.get());
585 }
586 // Confirm that the entry ID matches what was requested
587 std::string idStr;
588 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
589 {
590 messages::resourceMissingAtURI(asyncResp->res, entryID);
591 return;
592 }
593
594 // only use journal entries that contain a REDFISH_MESSAGE_ID
595 // field
596 boost::string_view messageID;
597 ret =
598 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
599 if (ret < 0)
600 {
601 messages::resourceNotFound(asyncResp->res, "LogEntry", name);
602 return;
603 }
604
605 if (fillEventLogEntryJson(name, entryID, messageID, journal.get(),
606 asyncResp->res.jsonValue) != 0)
607 {
608 messages::internalError(asyncResp->res);
609 return;
610 }
611 }
612};
613
614class BMCLogServiceCollection : public Node
615{
616 public:
617 template <typename CrowApp>
618 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700619 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700620 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700621 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700622 {boost::beast::http::verb::get, {{"Login"}}},
623 {boost::beast::http::verb::head, {{"Login"}}},
624 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
625 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
626 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
627 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700628 }
629
630 private:
631 /**
632 * Functions triggers appropriate requests on DBus
633 */
634 void doGet(crow::Response &res, const crow::Request &req,
635 const std::vector<std::string> &params) override
636 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700637 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700638 // Collections don't include the static data added by SubRoute because
639 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700640 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700641 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700642 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800643 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700644 asyncResp->res.jsonValue["@odata.id"] =
645 "/redfish/v1/Managers/bmc/LogServices";
646 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
647 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700648 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800649 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
650 logServiceArray = nlohmann::json::array();
651#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
652 logServiceArray.push_back(
653 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
654#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700655#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800656 logServiceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700657 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700658#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700659 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800660 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700661 }
662};
663
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800664class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700665{
666 public:
667 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800668 BMCJournalLogService(CrowApp &app) :
669 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700670 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700671 entityPrivileges = {
672 {boost::beast::http::verb::get, {{"Login"}}},
673 {boost::beast::http::verb::head, {{"Login"}}},
674 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
675 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
676 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
677 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
678 }
679
680 private:
681 void doGet(crow::Response &res, const crow::Request &req,
682 const std::vector<std::string> &params) override
683 {
684 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700685 asyncResp->res.jsonValue["@odata.type"] =
686 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800687 asyncResp->res.jsonValue["@odata.id"] =
688 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700689 asyncResp->res.jsonValue["@odata.context"] =
690 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800691 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
692 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
693 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700694 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
695 }
696};
697
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800698static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
699 sd_journal *journal,
700 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700701{
702 // Get the Log Entry contents
703 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700704
Jason M. Bills16428a12018-11-02 12:42:29 -0700705 boost::string_view msg;
706 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700707 if (ret < 0)
708 {
709 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
710 return 1;
711 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700712
713 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700714 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700715 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700716 if (ret < 0)
717 {
718 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
719 return 1;
720 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700721
722 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700723 std::string entryTimeStr;
724 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700725 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700726 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700727 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700728
729 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800730 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700731 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
732 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800733 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
734 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700735 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800736 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700737 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700738 {"EntryType", "Oem"},
739 {"Severity",
740 severity <= 2 ? "Critical"
741 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
742 {"OemRecordFormat", "Intel BMC Journal Entry"},
743 {"Created", std::move(entryTimeStr)}};
744 return 0;
745}
746
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800747class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700748{
749 public:
750 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800751 BMCJournalLogEntryCollection(CrowApp &app) :
752 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700753 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700754 entityPrivileges = {
755 {boost::beast::http::verb::get, {{"Login"}}},
756 {boost::beast::http::verb::head, {{"Login"}}},
757 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
758 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
759 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
760 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
761 }
762
763 private:
764 void doGet(crow::Response &res, const crow::Request &req,
765 const std::vector<std::string> &params) override
766 {
767 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700768 static constexpr const long maxEntriesPerPage = 1000;
769 long skip = 0;
770 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700771 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700772 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700773 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700774 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700775 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700776 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700777 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700778 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700779 // Collections don't include the static data added by SubRoute because
780 // it has a duplicate entry for members
781 asyncResp->res.jsonValue["@odata.type"] =
782 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -0800783 asyncResp->res.jsonValue["@odata.id"] =
784 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700785 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800786 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700787 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800788 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700789 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
790 asyncResp->res.jsonValue["Description"] =
791 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -0800792 asyncResp->res.jsonValue["@odata.id"] =
793 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700794 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
795 logEntryArray = nlohmann::json::array();
796
797 // Go through the journal and use the timestamp to create a unique ID
798 // for each entry
799 sd_journal *journalTmp = nullptr;
800 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
801 if (ret < 0)
802 {
803 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700804 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700805 return;
806 }
807 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
808 journalTmp, sd_journal_close);
809 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700810 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700811 SD_JOURNAL_FOREACH(journal.get())
812 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700813 entryCount++;
814 // Handle paging using skip (number of entries to skip from the
815 // start) and top (number of entries to display)
816 if (entryCount <= skip || entryCount > skip + top)
817 {
818 continue;
819 }
820
Jason M. Bills16428a12018-11-02 12:42:29 -0700821 std::string idStr;
822 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700823 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700824 continue;
825 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700826
Jason M. Billse1f26342018-07-18 12:12:00 -0700827 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800828 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
829 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
830 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700831 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700832 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700833 return;
834 }
835 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700836 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
837 if (skip + top < entryCount)
838 {
839 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800840 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700841 std::to_string(skip + top);
842 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700843 }
844};
845
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800846class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700847{
848 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800849 BMCJournalLogEntry(CrowApp &app) :
850 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700851 std::string())
852 {
853 entityPrivileges = {
854 {boost::beast::http::verb::get, {{"Login"}}},
855 {boost::beast::http::verb::head, {{"Login"}}},
856 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
857 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
858 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
859 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
860 }
861
862 private:
863 void doGet(crow::Response &res, const crow::Request &req,
864 const std::vector<std::string> &params) override
865 {
866 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
867 if (params.size() != 1)
868 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700869 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700870 return;
871 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700872 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700873 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700874 uint64_t ts = 0;
875 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700876 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700877 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700878 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700879 }
880
881 sd_journal *journalTmp = nullptr;
882 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
883 if (ret < 0)
884 {
885 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700886 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700887 return;
888 }
889 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
890 journalTmp, sd_journal_close);
891 journalTmp = nullptr;
892 // Go to the timestamp in the log and move to the entry at the index
893 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
894 for (int i = 0; i <= index; i++)
895 {
896 sd_journal_next(journal.get());
897 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800898 // Confirm that the entry ID matches what was requested
899 std::string idStr;
900 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
901 {
902 messages::resourceMissingAtURI(asyncResp->res, entryID);
903 return;
904 }
905
906 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
907 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700908 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700909 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700910 return;
911 }
912 }
913};
914
915class CPULogService : public Node
916{
917 public:
918 template <typename CrowApp>
919 CPULogService(CrowApp &app) :
920 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700921 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700922 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700923 {boost::beast::http::verb::get, {{"Login"}}},
924 {boost::beast::http::verb::head, {{"Login"}}},
925 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
926 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
927 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
928 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700929 }
930
931 private:
932 /**
933 * Functions triggers appropriate requests on DBus
934 */
935 void doGet(crow::Response &res, const crow::Request &req,
936 const std::vector<std::string> &params) override
937 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700938 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700939 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -0800940 asyncResp->res.jsonValue["@odata.id"] =
941 "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Jason M. Billse1f26342018-07-18 12:12:00 -0700942 asyncResp->res.jsonValue["@odata.type"] =
943 "#LogService.v1_1_0.LogService";
944 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800945 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700946 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
947 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
948 asyncResp->res.jsonValue["Id"] = "CPU Log";
949 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
950 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
951 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700952 {"Oem",
953 {{"#CpuLog.Immediate",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800954 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/"
955 "Actions/Oem/CpuLog.Immediate"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700956
957#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700958 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700959 {"#CpuLog.SendRawPeci",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800960 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/"
961 "Oem/CpuLog.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700962#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700963 }
964};
965
Jason M. Billse1f26342018-07-18 12:12:00 -0700966class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700967{
968 public:
969 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700970 CPULogEntryCollection(CrowApp &app) :
971 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700972 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700973 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700974 {boost::beast::http::verb::get, {{"Login"}}},
975 {boost::beast::http::verb::head, {{"Login"}}},
976 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
977 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
978 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
979 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700980 }
981
982 private:
983 /**
984 * Functions triggers appropriate requests on DBus
985 */
986 void doGet(crow::Response &res, const crow::Request &req,
987 const std::vector<std::string> &params) override
988 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700989 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700990 // Collections don't include the static data added by SubRoute because
991 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700992 auto getLogEntriesCallback = [asyncResp](
993 const boost::system::error_code ec,
994 const std::vector<std::string> &resp) {
995 if (ec)
996 {
997 if (ec.value() !=
998 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -0700999 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001000 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1001 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001002 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001003 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001004 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001005 }
1006 asyncResp->res.jsonValue["@odata.type"] =
1007 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001008 asyncResp->res.jsonValue["@odata.id"] =
1009 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001010 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001011 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001012 asyncResp->res.jsonValue["@odata.id"] =
1013 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1014 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1015 asyncResp->res.jsonValue["Description"] =
1016 "Collection of CPU Log Entries";
1017 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1018 logEntryArray = nlohmann::json::array();
1019 for (const std::string &objpath : resp)
1020 {
1021 // Don't list the immediate log
1022 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001023 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001024 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001025 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001026 std::size_t lastPos = objpath.rfind("/");
1027 if (lastPos != std::string::npos)
1028 {
1029 logEntryArray.push_back(
1030 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
1031 "CpuLog/Entries/" +
1032 objpath.substr(lastPos + 1)}});
1033 }
1034 }
1035 asyncResp->res.jsonValue["Members@odata.count"] =
1036 logEntryArray.size();
1037 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001038 crow::connections::systemBus->async_method_call(
1039 std::move(getLogEntriesCallback),
1040 "xyz.openbmc_project.ObjectMapper",
1041 "/xyz/openbmc_project/object_mapper",
1042 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001043 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001044 }
1045};
1046
1047std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1048{
1049 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
1050 if (metaIt != cpuLog.end())
1051 {
1052 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
1053 if (tsIt != metaIt->end())
1054 {
1055 const std::string *logTime = tsIt->get_ptr<const std::string *>();
1056 if (logTime != nullptr)
1057 {
1058 return *logTime;
1059 }
1060 }
1061 }
1062 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1063
1064 return std::string();
1065}
1066
Jason M. Billse1f26342018-07-18 12:12:00 -07001067class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001068{
1069 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001070 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001071 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001072 std::string())
1073 {
1074 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001075 {boost::beast::http::verb::get, {{"Login"}}},
1076 {boost::beast::http::verb::head, {{"Login"}}},
1077 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1078 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1079 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1080 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001081 }
1082
1083 private:
1084 void doGet(crow::Response &res, const crow::Request &req,
1085 const std::vector<std::string> &params) override
1086 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001087 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001088 if (params.size() != 1)
1089 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001090 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001091 return;
1092 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001093 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001094 auto getStoredLogCallback = [asyncResp, logId](
1095 const boost::system::error_code ec,
1096 const std::variant<std::string> &resp) {
1097 if (ec)
1098 {
1099 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1100 messages::internalError(asyncResp->res);
1101 return;
1102 }
1103 const std::string *log = std::get_if<std::string>(&resp);
1104 if (log == nullptr)
1105 {
1106 messages::internalError(asyncResp->res);
1107 return;
1108 }
1109 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1110 if (j.is_discarded())
1111 {
1112 messages::internalError(asyncResp->res);
1113 return;
1114 }
1115 std::string t = getLogCreatedTime(j);
1116 asyncResp->res.jsonValue = {
1117 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1118 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1119 {"@odata.id",
1120 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1121 std::to_string(logId)},
1122 {"Name", "CPU Debug Log"},
1123 {"Id", logId},
1124 {"EntryType", "Oem"},
1125 {"OemRecordFormat", "Intel CPU Log"},
1126 {"Oem", {{"Intel", std::move(j)}}},
1127 {"Created", std::move(t)}};
1128 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001129 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001130 std::move(getStoredLogCallback), cpuLogObject,
1131 cpuLogPath + std::string("/") + std::to_string(logId),
1132 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001133 }
1134};
1135
Jason M. Billse1f26342018-07-18 12:12:00 -07001136class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001137{
1138 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001139 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001140 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001141 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001142 {
1143 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001144 {boost::beast::http::verb::get, {{"Login"}}},
1145 {boost::beast::http::verb::head, {{"Login"}}},
1146 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1147 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1148 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1149 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001150 }
1151
1152 private:
1153 void doPost(crow::Response &res, const crow::Request &req,
1154 const std::vector<std::string> &params) override
1155 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001156 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001157 static std::unique_ptr<sdbusplus::bus::match::match>
1158 immediateLogMatcher;
1159
1160 // Only allow one Immediate Log request at a time
1161 if (immediateLogMatcher != nullptr)
1162 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001163 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001164 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001165 return;
1166 }
1167 // Make this static so it survives outside this method
1168 static boost::asio::deadline_timer timeout(*req.ioService);
1169
1170 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001171 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001172 immediateLogMatcher = nullptr;
1173 if (ec)
1174 {
1175 // operation_aborted is expected if timer is canceled before
1176 // completion.
1177 if (ec != boost::asio::error::operation_aborted)
1178 {
1179 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1180 }
1181 return;
1182 }
1183 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1184
Jason M. Billsf12894f2018-10-09 12:45:45 -07001185 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001186 });
1187
Jason M. Billse1f26342018-07-18 12:12:00 -07001188 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001189 sdbusplus::message::message &m) {
1190 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1191 boost::system::error_code ec;
1192 timeout.cancel(ec);
1193 if (ec)
1194 {
1195 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1196 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001197 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001198 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001199 std::string, boost::container::flat_map<
1200 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001201 interfacesAdded;
1202 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001203 const std::string *log = std::get_if<std::string>(
1204 &interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001205 if (log == nullptr)
1206 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001207 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001208 // Careful with immediateLogMatcher. It is a unique_ptr to the
1209 // match object inside which this lambda is executing. Once it
1210 // is set to nullptr, the match object will be destroyed and the
1211 // lambda will lose its context, including res, so it needs to
1212 // be the last thing done.
1213 immediateLogMatcher = nullptr;
1214 return;
1215 }
1216 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1217 if (j.is_discarded())
1218 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001219 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001220 // Careful with immediateLogMatcher. It is a unique_ptr to the
1221 // match object inside which this lambda is executing. Once it
1222 // is set to nullptr, the match object will be destroyed and the
1223 // lambda will lose its context, including res, so it needs to
1224 // be the last thing done.
1225 immediateLogMatcher = nullptr;
1226 return;
1227 }
1228 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001229 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001230 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1231 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1232 {"Name", "CPU Debug Log"},
1233 {"EntryType", "Oem"},
1234 {"OemRecordFormat", "Intel CPU Log"},
1235 {"Oem", {{"Intel", std::move(j)}}},
1236 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001237 // Careful with immediateLogMatcher. It is a unique_ptr to the
1238 // match object inside which this lambda is executing. Once it is
1239 // set to nullptr, the match object will be destroyed and the lambda
1240 // will lose its context, including res, so it needs to be the last
1241 // thing done.
1242 immediateLogMatcher = nullptr;
1243 };
1244 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1245 *crow::connections::systemBus,
1246 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001247 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001248 std::move(immediateLogMatcherCallback));
1249
1250 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001251 [asyncResp](const boost::system::error_code ec,
1252 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001253 if (ec)
1254 {
1255 if (ec.value() ==
1256 boost::system::errc::operation_not_supported)
1257 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001258 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001259 }
1260 else
1261 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001262 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001263 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001264 boost::system::error_code timeoutec;
1265 timeout.cancel(timeoutec);
1266 if (timeoutec)
1267 {
1268 BMCWEB_LOG_ERROR << "error canceling timer "
1269 << timeoutec;
1270 }
1271 immediateLogMatcher = nullptr;
1272 return;
1273 }
1274 };
1275 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001276 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1277 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001278 }
1279};
1280
Jason M. Billse1f26342018-07-18 12:12:00 -07001281class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001282{
1283 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001284 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001285 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001286 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001287 {
1288 entityPrivileges = {
1289 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1290 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1291 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1292 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1293 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1294 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1295 }
1296
1297 private:
1298 void doPost(crow::Response &res, const crow::Request &req,
1299 const std::vector<std::string> &params) override
1300 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001301 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001302 uint8_t clientAddress = 0;
1303 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001304 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001305 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1306 "ReadLength", readLength, "PECICommand",
1307 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001308 {
Ed Tanousb1556422018-10-16 14:09:17 -07001309 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001310 }
Ed Tanousb1556422018-10-16 14:09:17 -07001311
Ed Tanous1da66f72018-07-27 16:13:37 -07001312 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001313 auto sendRawPECICallback =
1314 [asyncResp](const boost::system::error_code ec,
1315 const std::vector<uint8_t> &resp) {
1316 if (ec)
1317 {
1318 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1319 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001320 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001321 return;
1322 }
1323 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1324 {"PECIResponse", resp}};
1325 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001326 // Call the SendRawPECI command with the provided data
1327 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001328 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1329 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001330 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001331 }
1332};
1333
1334} // namespace redfish