blob: bcdb80c4641a8caf48258426c4c450fa27e48186 [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) :
Ed Tanous029573d2019-02-01 10:57:49 -0800263 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800264 {
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);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800282 // 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"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800289 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800290 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();
Ed Tanous029573d2019-02-01 10:57:49 -0800295 logServiceArray.push_back(
296 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800297 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) :
Ed Tanous029573d2019-02-01 10:57:49 -0800307 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800308 {
309 entityPrivileges = {
310 {boost::beast::http::verb::get, {{"Login"}}},
311 {boost::beast::http::verb::head, {{"Login"}}},
312 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
313 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
314 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
316 }
317
318 private:
319 void doGet(crow::Response &res, const crow::Request &req,
320 const std::vector<std::string> &params) override
321 {
322 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
323
324 const std::string &name = params[0];
325 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800326 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800327 asyncResp->res.jsonValue["@odata.type"] =
328 "#LogService.v1_1_0.LogService";
329 asyncResp->res.jsonValue["@odata.context"] =
330 "/redfish/v1/$metadata#LogService.LogService";
331 asyncResp->res.jsonValue["Name"] = "Event Log Service";
332 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
333 asyncResp->res.jsonValue["Id"] = "Event Log";
334 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
335 asyncResp->res.jsonValue["Entries"] = {
336 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800337 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800338 }
339};
340
Ed Tanous029573d2019-02-01 10:57:49 -0800341static int fillEventLogEntryJson(const std::string &bmcLogEntryID,
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800342 const boost::string_view &messageID,
343 sd_journal *journal,
344 nlohmann::json &bmcLogEntryJson)
345{
346 // Get the Log Entry contents
347 int ret = 0;
348
349 boost::string_view msg;
350 ret = getJournalMetadata(journal, "MESSAGE", msg);
351 if (ret < 0)
352 {
353 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
354 return 1;
355 }
356
357 // Get the severity from the PRIORITY field
358 int severity = 8; // Default to an invalid priority
359 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
360 if (ret < 0)
361 {
362 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
363 return 1;
364 }
365
366 // Get the MessageArgs from the journal entry by finding all of the
367 // REDFISH_MESSAGE_ARG_x fields
368 const void *data;
369 size_t length;
370 std::vector<std::string> messageArgs;
371 SD_JOURNAL_FOREACH_DATA(journal, data, length)
372 {
373 boost::string_view field(static_cast<const char *>(data), length);
374 if (field.starts_with("REDFISH_MESSAGE_ARG_"))
375 {
376 // Get the Arg number from the field name
377 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1);
378 if (field.empty())
379 {
380 continue;
381 }
382 int argNum = std::strtoul(field.data(), nullptr, 10);
383 if (argNum == 0)
384 {
385 continue;
386 }
387 // Get the Arg value after the "=" character.
388 field.remove_prefix(std::min(field.find("=") + 1, field.size()));
389 // Make sure we have enough space in messageArgs
390 if (argNum > messageArgs.size())
391 {
392 messageArgs.resize(argNum);
393 }
394 messageArgs[argNum - 1] = field.to_string();
395 }
396 }
397
398 // Get the Created time from the timestamp
399 std::string entryTimeStr;
400 if (!getEntryTimestamp(journal, entryTimeStr))
401 {
402 return 1;
403 }
404
405 // Fill in the log entry with the gathered data
406 bmcLogEntryJson = {
407 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
408 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800409 {"@odata.id",
410 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
411 bmcLogEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800412 {"Name", "System Event Log Entry"},
413 {"Id", bmcLogEntryID},
414 {"Message", msg},
415 {"MessageId", messageID},
416 {"MessageArgs", std::move(messageArgs)},
417 {"EntryType", "Event"},
418 {"Severity",
419 severity <= 2 ? "Critical"
420 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
421 {"Created", std::move(entryTimeStr)}};
422 return 0;
423}
424
425class EventLogEntryCollection : public Node
426{
427 public:
428 template <typename CrowApp>
429 EventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800430 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800431 {
432 entityPrivileges = {
433 {boost::beast::http::verb::get, {{"Login"}}},
434 {boost::beast::http::verb::head, {{"Login"}}},
435 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
436 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
437 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
438 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
439 }
440
441 private:
442 void doGet(crow::Response &res, const crow::Request &req,
443 const std::vector<std::string> &params) override
444 {
445 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
446 long skip = 0;
447 long top = maxEntriesPerPage; // Show max entries by default
448 if (!getSkipParam(asyncResp->res, req, skip))
449 {
450 return;
451 }
452 if (!getTopParam(asyncResp->res, req, top))
453 {
454 return;
455 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800456 // Collections don't include the static data added by SubRoute because
457 // it has a duplicate entry for members
458 asyncResp->res.jsonValue["@odata.type"] =
459 "#LogEntryCollection.LogEntryCollection";
460 asyncResp->res.jsonValue["@odata.context"] =
461 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
462 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800463 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800464 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
465 asyncResp->res.jsonValue["Description"] =
466 "Collection of System Event Log Entries";
467 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
468 logEntryArray = nlohmann::json::array();
469
470 // Go through the journal and create a unique ID for each entry
471 sd_journal *journalTmp = nullptr;
472 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
473 if (ret < 0)
474 {
475 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
476 messages::internalError(asyncResp->res);
477 return;
478 }
479 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
480 journalTmp, sd_journal_close);
481 journalTmp = nullptr;
482 uint64_t entryCount = 0;
483 SD_JOURNAL_FOREACH(journal.get())
484 {
485 // Look for only journal entries that contain a REDFISH_MESSAGE_ID
486 // field
487 boost::string_view messageID;
488 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID",
489 messageID);
490 if (ret < 0)
491 {
492 continue;
493 }
494
495 entryCount++;
496 // Handle paging using skip (number of entries to skip from the
497 // start) and top (number of entries to display)
498 if (entryCount <= skip || entryCount > skip + top)
499 {
500 continue;
501 }
502
503 std::string idStr;
504 if (!getUniqueEntryID(journal.get(), idStr))
505 {
506 continue;
507 }
508
509 logEntryArray.push_back({});
510 nlohmann::json &bmcLogEntry = logEntryArray.back();
Ed Tanous029573d2019-02-01 10:57:49 -0800511 if (fillEventLogEntryJson(idStr, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800512 bmcLogEntry) != 0)
513 {
514 messages::internalError(asyncResp->res);
515 return;
516 }
517 }
518 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
519 if (skip + top < entryCount)
520 {
521 asyncResp->res.jsonValue["Members@odata.nextLink"] =
522 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
523 std::to_string(skip + top);
524 }
525 }
526};
527
528class EventLogEntry : public Node
529{
530 public:
531 EventLogEntry(CrowApp &app) :
532 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800533 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
534 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800535 {
536 entityPrivileges = {
537 {boost::beast::http::verb::get, {{"Login"}}},
538 {boost::beast::http::verb::head, {{"Login"}}},
539 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
540 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
541 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
542 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
543 }
544
545 private:
546 void doGet(crow::Response &res, const crow::Request &req,
547 const std::vector<std::string> &params) override
548 {
549 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -0800550 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800551 {
552 messages::internalError(asyncResp->res);
553 return;
554 }
Ed Tanous029573d2019-02-01 10:57:49 -0800555 const std::string &entryID = params[0];
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800556 // Convert the unique ID back to a timestamp to find the entry
557 uint64_t ts = 0;
558 uint16_t index = 0;
559 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
560 {
561 return;
562 }
563
564 sd_journal *journalTmp = nullptr;
565 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
566 if (ret < 0)
567 {
568 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
569 messages::internalError(asyncResp->res);
570 return;
571 }
572 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
573 journalTmp, sd_journal_close);
574 journalTmp = nullptr;
575 // Go to the timestamp in the log and move to the entry at the index
576 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
577 for (int i = 0; i <= index; i++)
578 {
579 sd_journal_next(journal.get());
580 }
581 // Confirm that the entry ID matches what was requested
582 std::string idStr;
583 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
584 {
585 messages::resourceMissingAtURI(asyncResp->res, entryID);
586 return;
587 }
588
Jason M. Billscd50aa42019-02-12 17:09:02 -0800589 // only use journal entries that contain a REDFISH_MESSAGE_ID field
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800590 boost::string_view messageID;
591 ret =
592 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
593 if (ret < 0)
594 {
Ed Tanous029573d2019-02-01 10:57:49 -0800595 messages::resourceNotFound(asyncResp->res, "LogEntry", "system");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800596 return;
597 }
598
Ed Tanous029573d2019-02-01 10:57:49 -0800599 if (fillEventLogEntryJson(entryID, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800600 asyncResp->res.jsonValue) != 0)
601 {
602 messages::internalError(asyncResp->res);
603 return;
604 }
605 }
606};
607
608class BMCLogServiceCollection : public Node
609{
610 public:
611 template <typename CrowApp>
612 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700613 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700614 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700615 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700616 {boost::beast::http::verb::get, {{"Login"}}},
617 {boost::beast::http::verb::head, {{"Login"}}},
618 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
619 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
620 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
621 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700622 }
623
624 private:
625 /**
626 * Functions triggers appropriate requests on DBus
627 */
628 void doGet(crow::Response &res, const crow::Request &req,
629 const std::vector<std::string> &params) override
630 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700631 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700632 // Collections don't include the static data added by SubRoute because
633 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700634 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700635 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700636 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800637 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700638 asyncResp->res.jsonValue["@odata.id"] =
639 "/redfish/v1/Managers/bmc/LogServices";
640 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
641 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700642 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800643 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
644 logServiceArray = nlohmann::json::array();
645#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
646 logServiceArray.push_back(
647 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
648#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700649#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800650 logServiceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700651 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700652#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700653 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800654 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700655 }
656};
657
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800658class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700659{
660 public:
661 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800662 BMCJournalLogService(CrowApp &app) :
663 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700664 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700665 entityPrivileges = {
666 {boost::beast::http::verb::get, {{"Login"}}},
667 {boost::beast::http::verb::head, {{"Login"}}},
668 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
669 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
670 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
671 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
672 }
673
674 private:
675 void doGet(crow::Response &res, const crow::Request &req,
676 const std::vector<std::string> &params) override
677 {
678 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700679 asyncResp->res.jsonValue["@odata.type"] =
680 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800681 asyncResp->res.jsonValue["@odata.id"] =
682 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700683 asyncResp->res.jsonValue["@odata.context"] =
684 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800685 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
686 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
687 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700688 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -0800689 asyncResp->res.jsonValue["Entries"] = {
690 {"@odata.id",
691 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}};
Jason M. Billse1f26342018-07-18 12:12:00 -0700692 }
693};
694
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800695static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
696 sd_journal *journal,
697 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700698{
699 // Get the Log Entry contents
700 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700701
Jason M. Bills16428a12018-11-02 12:42:29 -0700702 boost::string_view msg;
703 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700704 if (ret < 0)
705 {
706 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
707 return 1;
708 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700709
710 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700711 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700712 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700713 if (ret < 0)
714 {
715 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
716 return 1;
717 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700718
719 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700720 std::string entryTimeStr;
721 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700722 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700723 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700724 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700725
726 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800727 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700728 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
729 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800730 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
731 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700732 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800733 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700734 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700735 {"EntryType", "Oem"},
736 {"Severity",
737 severity <= 2 ? "Critical"
738 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
739 {"OemRecordFormat", "Intel BMC Journal Entry"},
740 {"Created", std::move(entryTimeStr)}};
741 return 0;
742}
743
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800744class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700745{
746 public:
747 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800748 BMCJournalLogEntryCollection(CrowApp &app) :
749 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700750 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700751 entityPrivileges = {
752 {boost::beast::http::verb::get, {{"Login"}}},
753 {boost::beast::http::verb::head, {{"Login"}}},
754 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
755 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
756 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
757 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
758 }
759
760 private:
761 void doGet(crow::Response &res, const crow::Request &req,
762 const std::vector<std::string> &params) override
763 {
764 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700765 static constexpr const long maxEntriesPerPage = 1000;
766 long skip = 0;
767 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700768 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700769 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700770 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700771 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700772 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700773 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700774 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700775 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700776 // Collections don't include the static data added by SubRoute because
777 // it has a duplicate entry for members
778 asyncResp->res.jsonValue["@odata.type"] =
779 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -0800780 asyncResp->res.jsonValue["@odata.id"] =
781 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700782 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800783 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700784 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800785 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700786 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
787 asyncResp->res.jsonValue["Description"] =
788 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -0800789 asyncResp->res.jsonValue["@odata.id"] =
790 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700791 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
792 logEntryArray = nlohmann::json::array();
793
794 // Go through the journal and use the timestamp to create a unique ID
795 // for each entry
796 sd_journal *journalTmp = nullptr;
797 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
798 if (ret < 0)
799 {
800 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700801 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700802 return;
803 }
804 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
805 journalTmp, sd_journal_close);
806 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700807 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700808 SD_JOURNAL_FOREACH(journal.get())
809 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700810 entryCount++;
811 // Handle paging using skip (number of entries to skip from the
812 // start) and top (number of entries to display)
813 if (entryCount <= skip || entryCount > skip + top)
814 {
815 continue;
816 }
817
Jason M. Bills16428a12018-11-02 12:42:29 -0700818 std::string idStr;
819 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700820 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700821 continue;
822 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700823
Jason M. Billse1f26342018-07-18 12:12:00 -0700824 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800825 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
826 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
827 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700828 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700829 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700830 return;
831 }
832 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700833 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
834 if (skip + top < entryCount)
835 {
836 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800837 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700838 std::to_string(skip + top);
839 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700840 }
841};
842
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800843class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700844{
845 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800846 BMCJournalLogEntry(CrowApp &app) :
847 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700848 std::string())
849 {
850 entityPrivileges = {
851 {boost::beast::http::verb::get, {{"Login"}}},
852 {boost::beast::http::verb::head, {{"Login"}}},
853 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
854 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
855 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
856 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
857 }
858
859 private:
860 void doGet(crow::Response &res, const crow::Request &req,
861 const std::vector<std::string> &params) override
862 {
863 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
864 if (params.size() != 1)
865 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700866 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700867 return;
868 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700869 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700870 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700871 uint64_t ts = 0;
872 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700873 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700874 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700875 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700876 }
877
878 sd_journal *journalTmp = nullptr;
879 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
880 if (ret < 0)
881 {
882 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700883 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700884 return;
885 }
886 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
887 journalTmp, sd_journal_close);
888 journalTmp = nullptr;
889 // Go to the timestamp in the log and move to the entry at the index
890 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
891 for (int i = 0; i <= index; i++)
892 {
893 sd_journal_next(journal.get());
894 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800895 // Confirm that the entry ID matches what was requested
896 std::string idStr;
897 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
898 {
899 messages::resourceMissingAtURI(asyncResp->res, entryID);
900 return;
901 }
902
903 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
904 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700905 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700906 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700907 return;
908 }
909 }
910};
911
912class CPULogService : public Node
913{
914 public:
915 template <typename CrowApp>
916 CPULogService(CrowApp &app) :
917 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700918 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700919 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700920 {boost::beast::http::verb::get, {{"Login"}}},
921 {boost::beast::http::verb::head, {{"Login"}}},
922 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
923 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
924 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
925 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700926 }
927
928 private:
929 /**
930 * Functions triggers appropriate requests on DBus
931 */
932 void doGet(crow::Response &res, const crow::Request &req,
933 const std::vector<std::string> &params) override
934 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700935 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700936 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -0800937 asyncResp->res.jsonValue["@odata.id"] =
938 "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Jason M. Billse1f26342018-07-18 12:12:00 -0700939 asyncResp->res.jsonValue["@odata.type"] =
940 "#LogService.v1_1_0.LogService";
941 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800942 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700943 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
944 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
945 asyncResp->res.jsonValue["Id"] = "CPU Log";
946 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
947 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -0800948 asyncResp->res.jsonValue["Entries"] = {
949 {"@odata.id",
950 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -0700951 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{
Jason M. Billsc4d00432019-02-12 17:17:48 -08001049 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data");
1050 if (cdIt != cpuLog.end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001051 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001052 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1053 if (siIt != cdIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001054 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001055 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1056 if (tsIt != siIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001057 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001058 const std::string *logTime =
1059 tsIt->get_ptr<const std::string *>();
1060 if (logTime != nullptr)
1061 {
1062 return *logTime;
1063 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001064 }
1065 }
1066 }
1067 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1068
1069 return std::string();
1070}
1071
Jason M. Billse1f26342018-07-18 12:12:00 -07001072class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001073{
1074 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001075 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001076 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001077 std::string())
1078 {
1079 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001080 {boost::beast::http::verb::get, {{"Login"}}},
1081 {boost::beast::http::verb::head, {{"Login"}}},
1082 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1083 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1084 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1085 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001086 }
1087
1088 private:
1089 void doGet(crow::Response &res, const crow::Request &req,
1090 const std::vector<std::string> &params) override
1091 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001092 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001093 if (params.size() != 1)
1094 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001095 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001096 return;
1097 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001098 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001099 auto getStoredLogCallback = [asyncResp, logId](
1100 const boost::system::error_code ec,
1101 const std::variant<std::string> &resp) {
1102 if (ec)
1103 {
1104 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1105 messages::internalError(asyncResp->res);
1106 return;
1107 }
1108 const std::string *log = std::get_if<std::string>(&resp);
1109 if (log == nullptr)
1110 {
1111 messages::internalError(asyncResp->res);
1112 return;
1113 }
1114 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1115 if (j.is_discarded())
1116 {
1117 messages::internalError(asyncResp->res);
1118 return;
1119 }
1120 std::string t = getLogCreatedTime(j);
1121 asyncResp->res.jsonValue = {
1122 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1123 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1124 {"@odata.id",
1125 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1126 std::to_string(logId)},
1127 {"Name", "CPU Debug Log"},
1128 {"Id", logId},
1129 {"EntryType", "Oem"},
1130 {"OemRecordFormat", "Intel CPU Log"},
1131 {"Oem", {{"Intel", std::move(j)}}},
1132 {"Created", std::move(t)}};
1133 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001134 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001135 std::move(getStoredLogCallback), cpuLogObject,
1136 cpuLogPath + std::string("/") + std::to_string(logId),
1137 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001138 }
1139};
1140
Jason M. Billse1f26342018-07-18 12:12:00 -07001141class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001142{
1143 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001144 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001145 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001146 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001147 {
1148 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001149 {boost::beast::http::verb::get, {{"Login"}}},
1150 {boost::beast::http::verb::head, {{"Login"}}},
1151 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1152 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1153 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1154 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001155 }
1156
1157 private:
1158 void doPost(crow::Response &res, const crow::Request &req,
1159 const std::vector<std::string> &params) override
1160 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001161 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001162 static std::unique_ptr<sdbusplus::bus::match::match>
1163 immediateLogMatcher;
1164
1165 // Only allow one Immediate Log request at a time
1166 if (immediateLogMatcher != nullptr)
1167 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001168 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001169 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001170 return;
1171 }
1172 // Make this static so it survives outside this method
1173 static boost::asio::deadline_timer timeout(*req.ioService);
1174
1175 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001176 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001177 immediateLogMatcher = nullptr;
1178 if (ec)
1179 {
1180 // operation_aborted is expected if timer is canceled before
1181 // completion.
1182 if (ec != boost::asio::error::operation_aborted)
1183 {
1184 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1185 }
1186 return;
1187 }
1188 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1189
Jason M. Billsf12894f2018-10-09 12:45:45 -07001190 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001191 });
1192
Jason M. Billse1f26342018-07-18 12:12:00 -07001193 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001194 sdbusplus::message::message &m) {
1195 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1196 boost::system::error_code ec;
1197 timeout.cancel(ec);
1198 if (ec)
1199 {
1200 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1201 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001202 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001203 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001204 std::string, boost::container::flat_map<
1205 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001206 interfacesAdded;
1207 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001208 const std::string *log = std::get_if<std::string>(
1209 &interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001210 if (log == nullptr)
1211 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001212 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001213 // Careful with immediateLogMatcher. It is a unique_ptr to the
1214 // match object inside which this lambda is executing. Once it
1215 // is set to nullptr, the match object will be destroyed and the
1216 // lambda will lose its context, including res, so it needs to
1217 // be the last thing done.
1218 immediateLogMatcher = nullptr;
1219 return;
1220 }
1221 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1222 if (j.is_discarded())
1223 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001224 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001225 // Careful with immediateLogMatcher. It is a unique_ptr to the
1226 // match object inside which this lambda is executing. Once it
1227 // is set to nullptr, the match object will be destroyed and the
1228 // lambda will lose its context, including res, so it needs to
1229 // be the last thing done.
1230 immediateLogMatcher = nullptr;
1231 return;
1232 }
1233 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001234 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001235 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1236 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1237 {"Name", "CPU Debug Log"},
1238 {"EntryType", "Oem"},
1239 {"OemRecordFormat", "Intel CPU Log"},
1240 {"Oem", {{"Intel", std::move(j)}}},
1241 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001242 // Careful with immediateLogMatcher. It is a unique_ptr to the
1243 // match object inside which this lambda is executing. Once it is
1244 // set to nullptr, the match object will be destroyed and the lambda
1245 // will lose its context, including res, so it needs to be the last
1246 // thing done.
1247 immediateLogMatcher = nullptr;
1248 };
1249 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1250 *crow::connections::systemBus,
1251 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001252 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001253 std::move(immediateLogMatcherCallback));
1254
1255 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001256 [asyncResp](const boost::system::error_code ec,
1257 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001258 if (ec)
1259 {
1260 if (ec.value() ==
1261 boost::system::errc::operation_not_supported)
1262 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001263 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001264 }
1265 else
1266 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001267 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001268 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001269 boost::system::error_code timeoutec;
1270 timeout.cancel(timeoutec);
1271 if (timeoutec)
1272 {
1273 BMCWEB_LOG_ERROR << "error canceling timer "
1274 << timeoutec;
1275 }
1276 immediateLogMatcher = nullptr;
1277 return;
1278 }
1279 };
1280 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001281 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1282 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001283 }
1284};
1285
Jason M. Billse1f26342018-07-18 12:12:00 -07001286class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001287{
1288 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001289 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001290 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001291 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001292 {
1293 entityPrivileges = {
1294 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1295 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1296 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1297 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1298 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1299 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1300 }
1301
1302 private:
1303 void doPost(crow::Response &res, const crow::Request &req,
1304 const std::vector<std::string> &params) override
1305 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001306 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001307 uint8_t clientAddress = 0;
1308 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001309 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001310 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1311 "ReadLength", readLength, "PECICommand",
1312 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001313 {
Ed Tanousb1556422018-10-16 14:09:17 -07001314 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001315 }
Ed Tanousb1556422018-10-16 14:09:17 -07001316
Ed Tanous1da66f72018-07-27 16:13:37 -07001317 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001318 auto sendRawPECICallback =
1319 [asyncResp](const boost::system::error_code ec,
1320 const std::vector<uint8_t> &resp) {
1321 if (ec)
1322 {
1323 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1324 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001325 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001326 return;
1327 }
1328 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1329 {"PECIResponse", resp}};
1330 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001331 // Call the SendRawPECI command with the provided data
1332 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001333 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1334 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001335 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001336 }
1337};
1338
1339} // namespace redfish