blob: 61cbd9e03da50879c0162b5e93b4cb077d85bc09 [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,
Ed Tanous39e77502019-03-04 17:35:53 -080042 const std::string_view &field,
43 std::string_view &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -070044{
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 }
Ed Tanous39e77502019-03-04 17:35:53 -080055 contents = std::string_view(data, length);
Jason M. Bills16428a12018-11-02 12:42:29 -070056 // 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,
Ed Tanous39e77502019-03-04 17:35:53 -080062 const std::string_view &field, const int &base,
Jason M. Bills16428a12018-11-02 12:42:29 -070063 int &contents)
64{
65 int ret = 0;
Ed Tanous39e77502019-03-04 17:35:53 -080066 std::string_view metadata;
Jason M. Bills16428a12018-11-02 12:42:29 -070067 // 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
Ed Tanous39e77502019-03-04 17:35:53 -080097 std::string_view t1(entryTime);
98 std::string_view t2(entryTime);
Jason M. Bills16428a12018-11-02 12:42:29 -070099 if (t1.size() > 2 && t2.size() > 2)
100 {
101 t1.remove_suffix(2);
102 t2.remove_prefix(t2.size() - 2);
103 }
Ed Tanous39e77502019-03-04 17:35:53 -0800104 entryTimestamp = std::string(t1) + ":" + std::string(t2);
Jason M. Bills16428a12018-11-02 12:42:29 -0700105 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
Ed Tanous39e77502019-03-04 17:35:53 -0800204 std::string_view tsStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700205
206 auto underscorePos = tsStr.find("_");
207 if (underscorePos != tsStr.npos)
208 {
209 // Timestamp has an index
210 tsStr.remove_suffix(tsStr.size() - underscorePos);
Ed Tanous39e77502019-03-04 17:35:53 -0800211 std::string_view indexStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700212 indexStr.remove_prefix(underscorePos + 1);
213 std::size_t pos;
214 try
215 {
Ed Tanous39e77502019-03-04 17:35:53 -0800216 index = std::stoul(std::string(indexStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700217 }
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 {
Ed Tanous39e77502019-03-04 17:35:53 -0800238 timestamp = std::stoull(std::string(tsStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700239 }
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. Billsd53dd412019-02-12 17:16:22 -0800297#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
298 logServiceArray.push_back(
299 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/CpuLog"}});
300#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800301 asyncResp->res.jsonValue["Members@odata.count"] =
302 logServiceArray.size();
303 }
304};
305
306class EventLogService : public Node
307{
308 public:
309 template <typename CrowApp>
310 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800311 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800312 {
313 entityPrivileges = {
314 {boost::beast::http::verb::get, {{"Login"}}},
315 {boost::beast::http::verb::head, {{"Login"}}},
316 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
317 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
318 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
319 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
320 }
321
322 private:
323 void doGet(crow::Response &res, const crow::Request &req,
324 const std::vector<std::string> &params) override
325 {
326 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
327
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800328 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800329 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800330 asyncResp->res.jsonValue["@odata.type"] =
331 "#LogService.v1_1_0.LogService";
332 asyncResp->res.jsonValue["@odata.context"] =
333 "/redfish/v1/$metadata#LogService.LogService";
334 asyncResp->res.jsonValue["Name"] = "Event Log Service";
335 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
336 asyncResp->res.jsonValue["Id"] = "Event Log";
337 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
338 asyncResp->res.jsonValue["Entries"] = {
339 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800340 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800341 }
342};
343
Ed Tanous029573d2019-02-01 10:57:49 -0800344static int fillEventLogEntryJson(const std::string &bmcLogEntryID,
Ed Tanous39e77502019-03-04 17:35:53 -0800345 const std::string_view &messageID,
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800346 sd_journal *journal,
347 nlohmann::json &bmcLogEntryJson)
348{
349 // Get the Log Entry contents
350 int ret = 0;
351
Ed Tanous39e77502019-03-04 17:35:53 -0800352 std::string_view msg;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800353 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 {
Ed Tanous39e77502019-03-04 17:35:53 -0800376 std::string_view field(static_cast<const char *>(data), length);
377 if (boost::starts_with(field, "REDFISH_MESSAGE_ARG_"))
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800378 {
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 }
Ed Tanous39e77502019-03-04 17:35:53 -0800397 messageArgs[argNum - 1] = std::string(field);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800398 }
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"},
Ed Tanous029573d2019-02-01 10:57:49 -0800412 {"@odata.id",
413 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
414 bmcLogEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800415 {"Name", "System Event Log Entry"},
416 {"Id", bmcLogEntryID},
417 {"Message", msg},
418 {"MessageId", messageID},
419 {"MessageArgs", std::move(messageArgs)},
420 {"EntryType", "Event"},
421 {"Severity",
422 severity <= 2 ? "Critical"
423 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
424 {"Created", std::move(entryTimeStr)}};
425 return 0;
426}
427
428class EventLogEntryCollection : public Node
429{
430 public:
431 template <typename CrowApp>
432 EventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800433 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800434 {
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 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800459 // Collections don't include the static data added by SubRoute because
460 // it has a duplicate entry for members
461 asyncResp->res.jsonValue["@odata.type"] =
462 "#LogEntryCollection.LogEntryCollection";
463 asyncResp->res.jsonValue["@odata.context"] =
464 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
465 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800466 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800467 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
468 asyncResp->res.jsonValue["Description"] =
469 "Collection of System Event Log Entries";
470 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
471 logEntryArray = nlohmann::json::array();
472
473 // Go through the journal and create a unique ID for each entry
474 sd_journal *journalTmp = nullptr;
475 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
476 if (ret < 0)
477 {
478 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
479 messages::internalError(asyncResp->res);
480 return;
481 }
482 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
483 journalTmp, sd_journal_close);
484 journalTmp = nullptr;
485 uint64_t entryCount = 0;
486 SD_JOURNAL_FOREACH(journal.get())
487 {
488 // Look for only journal entries that contain a REDFISH_MESSAGE_ID
489 // field
Ed Tanous39e77502019-03-04 17:35:53 -0800490 std::string_view messageID;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800491 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID",
492 messageID);
493 if (ret < 0)
494 {
495 continue;
496 }
497
498 entryCount++;
499 // Handle paging using skip (number of entries to skip from the
500 // start) and top (number of entries to display)
501 if (entryCount <= skip || entryCount > skip + top)
502 {
503 continue;
504 }
505
506 std::string idStr;
507 if (!getUniqueEntryID(journal.get(), idStr))
508 {
509 continue;
510 }
511
512 logEntryArray.push_back({});
513 nlohmann::json &bmcLogEntry = logEntryArray.back();
Ed Tanous029573d2019-02-01 10:57:49 -0800514 if (fillEventLogEntryJson(idStr, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800515 bmcLogEntry) != 0)
516 {
517 messages::internalError(asyncResp->res);
518 return;
519 }
520 }
521 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
522 if (skip + top < entryCount)
523 {
524 asyncResp->res.jsonValue["Members@odata.nextLink"] =
525 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
526 std::to_string(skip + top);
527 }
528 }
529};
530
531class EventLogEntry : public Node
532{
533 public:
534 EventLogEntry(CrowApp &app) :
535 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800536 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
537 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800538 {
539 entityPrivileges = {
540 {boost::beast::http::verb::get, {{"Login"}}},
541 {boost::beast::http::verb::head, {{"Login"}}},
542 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
543 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
544 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
545 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
546 }
547
548 private:
549 void doGet(crow::Response &res, const crow::Request &req,
550 const std::vector<std::string> &params) override
551 {
552 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -0800553 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800554 {
555 messages::internalError(asyncResp->res);
556 return;
557 }
Ed Tanous029573d2019-02-01 10:57:49 -0800558 const std::string &entryID = params[0];
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800559 // Convert the unique ID back to a timestamp to find the entry
560 uint64_t ts = 0;
561 uint16_t index = 0;
562 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
563 {
564 return;
565 }
566
567 sd_journal *journalTmp = nullptr;
568 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
569 if (ret < 0)
570 {
571 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
572 messages::internalError(asyncResp->res);
573 return;
574 }
575 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
576 journalTmp, sd_journal_close);
577 journalTmp = nullptr;
578 // Go to the timestamp in the log and move to the entry at the index
579 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
580 for (int i = 0; i <= index; i++)
581 {
582 sd_journal_next(journal.get());
583 }
584 // Confirm that the entry ID matches what was requested
585 std::string idStr;
586 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
587 {
588 messages::resourceMissingAtURI(asyncResp->res, entryID);
589 return;
590 }
591
Jason M. Billscd50aa42019-02-12 17:09:02 -0800592 // only use journal entries that contain a REDFISH_MESSAGE_ID field
Ed Tanous39e77502019-03-04 17:35:53 -0800593 std::string_view messageID;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800594 ret =
595 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
596 if (ret < 0)
597 {
Ed Tanous029573d2019-02-01 10:57:49 -0800598 messages::resourceNotFound(asyncResp->res, "LogEntry", "system");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800599 return;
600 }
601
Ed Tanous029573d2019-02-01 10:57:49 -0800602 if (fillEventLogEntryJson(entryID, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800603 asyncResp->res.jsonValue) != 0)
604 {
605 messages::internalError(asyncResp->res);
606 return;
607 }
608 }
609};
610
611class BMCLogServiceCollection : public Node
612{
613 public:
614 template <typename CrowApp>
615 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700616 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700617 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700618 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700619 {boost::beast::http::verb::get, {{"Login"}}},
620 {boost::beast::http::verb::head, {{"Login"}}},
621 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
622 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
623 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
624 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700625 }
626
627 private:
628 /**
629 * Functions triggers appropriate requests on DBus
630 */
631 void doGet(crow::Response &res, const crow::Request &req,
632 const std::vector<std::string> &params) override
633 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700634 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700635 // Collections don't include the static data added by SubRoute because
636 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700637 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700638 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700639 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800640 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700641 asyncResp->res.jsonValue["@odata.id"] =
642 "/redfish/v1/Managers/bmc/LogServices";
643 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
644 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700645 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800646 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
647 logServiceArray = nlohmann::json::array();
648#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
649 logServiceArray.push_back(
650 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
651#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700652 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800653 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700654 }
655};
656
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800657class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700658{
659 public:
660 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800661 BMCJournalLogService(CrowApp &app) :
662 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700663 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700664 entityPrivileges = {
665 {boost::beast::http::verb::get, {{"Login"}}},
666 {boost::beast::http::verb::head, {{"Login"}}},
667 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
668 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
669 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
670 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
671 }
672
673 private:
674 void doGet(crow::Response &res, const crow::Request &req,
675 const std::vector<std::string> &params) override
676 {
677 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700678 asyncResp->res.jsonValue["@odata.type"] =
679 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800680 asyncResp->res.jsonValue["@odata.id"] =
681 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700682 asyncResp->res.jsonValue["@odata.context"] =
683 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800684 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
685 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
686 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700687 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -0800688 asyncResp->res.jsonValue["Entries"] = {
689 {"@odata.id",
690 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}};
Jason M. Billse1f26342018-07-18 12:12:00 -0700691 }
692};
693
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800694static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
695 sd_journal *journal,
696 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700697{
698 // Get the Log Entry contents
699 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700700
Ed Tanous39e77502019-03-04 17:35:53 -0800701 std::string_view msg;
Jason M. Bills16428a12018-11-02 12:42:29 -0700702 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700703 if (ret < 0)
704 {
705 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
706 return 1;
707 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700708
709 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700710 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700711 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700712 if (ret < 0)
713 {
714 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
715 return 1;
716 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700717
718 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700719 std::string entryTimeStr;
720 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700721 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700722 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700723 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700724
725 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800726 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700727 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
728 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800729 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
730 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700731 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800732 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700733 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700734 {"EntryType", "Oem"},
735 {"Severity",
736 severity <= 2 ? "Critical"
737 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
738 {"OemRecordFormat", "Intel BMC Journal Entry"},
739 {"Created", std::move(entryTimeStr)}};
740 return 0;
741}
742
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800743class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700744{
745 public:
746 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800747 BMCJournalLogEntryCollection(CrowApp &app) :
748 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700749 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700750 entityPrivileges = {
751 {boost::beast::http::verb::get, {{"Login"}}},
752 {boost::beast::http::verb::head, {{"Login"}}},
753 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
754 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
755 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
756 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
757 }
758
759 private:
760 void doGet(crow::Response &res, const crow::Request &req,
761 const std::vector<std::string> &params) override
762 {
763 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700764 static constexpr const long maxEntriesPerPage = 1000;
765 long skip = 0;
766 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700767 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700768 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700769 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700770 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700771 if (!getTopParam(asyncResp->res, req, top))
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. Billse1f26342018-07-18 12:12:00 -0700775 // Collections don't include the static data added by SubRoute because
776 // it has a duplicate entry for members
777 asyncResp->res.jsonValue["@odata.type"] =
778 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -0800779 asyncResp->res.jsonValue["@odata.id"] =
780 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700781 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800782 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700783 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800784 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700785 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
786 asyncResp->res.jsonValue["Description"] =
787 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -0800788 asyncResp->res.jsonValue["@odata.id"] =
789 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700790 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
791 logEntryArray = nlohmann::json::array();
792
793 // Go through the journal and use the timestamp to create a unique ID
794 // for each entry
795 sd_journal *journalTmp = nullptr;
796 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
797 if (ret < 0)
798 {
799 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700800 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700801 return;
802 }
803 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
804 journalTmp, sd_journal_close);
805 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700806 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700807 SD_JOURNAL_FOREACH(journal.get())
808 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700809 entryCount++;
810 // Handle paging using skip (number of entries to skip from the
811 // start) and top (number of entries to display)
812 if (entryCount <= skip || entryCount > skip + top)
813 {
814 continue;
815 }
816
Jason M. Bills16428a12018-11-02 12:42:29 -0700817 std::string idStr;
818 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700819 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700820 continue;
821 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700822
Jason M. Billse1f26342018-07-18 12:12:00 -0700823 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800824 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
825 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
826 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700827 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700828 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700829 return;
830 }
831 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700832 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
833 if (skip + top < entryCount)
834 {
835 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800836 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700837 std::to_string(skip + top);
838 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700839 }
840};
841
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800842class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700843{
844 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800845 BMCJournalLogEntry(CrowApp &app) :
846 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700847 std::string())
848 {
849 entityPrivileges = {
850 {boost::beast::http::verb::get, {{"Login"}}},
851 {boost::beast::http::verb::head, {{"Login"}}},
852 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
853 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
854 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
855 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
856 }
857
858 private:
859 void doGet(crow::Response &res, const crow::Request &req,
860 const std::vector<std::string> &params) override
861 {
862 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
863 if (params.size() != 1)
864 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700865 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700866 return;
867 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700868 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700869 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700870 uint64_t ts = 0;
871 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700872 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700873 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700874 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700875 }
876
877 sd_journal *journalTmp = nullptr;
878 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
879 if (ret < 0)
880 {
881 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700882 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700883 return;
884 }
885 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
886 journalTmp, sd_journal_close);
887 journalTmp = nullptr;
888 // Go to the timestamp in the log and move to the entry at the index
889 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
890 for (int i = 0; i <= index; i++)
891 {
892 sd_journal_next(journal.get());
893 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800894 // Confirm that the entry ID matches what was requested
895 std::string idStr;
896 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
897 {
898 messages::resourceMissingAtURI(asyncResp->res, entryID);
899 return;
900 }
901
902 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
903 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700904 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700905 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700906 return;
907 }
908 }
909};
910
911class CPULogService : public Node
912{
913 public:
914 template <typename CrowApp>
915 CPULogService(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -0800916 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700917 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700918 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700919 {boost::beast::http::verb::get, {{"Login"}}},
920 {boost::beast::http::verb::head, {{"Login"}}},
921 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
922 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
923 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
924 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700925 }
926
927 private:
928 /**
929 * Functions triggers appropriate requests on DBus
930 */
931 void doGet(crow::Response &res, const crow::Request &req,
932 const std::vector<std::string> &params) override
933 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700934 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700935 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -0800936 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsd53dd412019-02-12 17:16:22 -0800937 "/redfish/v1/Systems/system/LogServices/CpuLog";
Jason M. Billse1f26342018-07-18 12:12:00 -0700938 asyncResp->res.jsonValue["@odata.type"] =
939 "#LogService.v1_1_0.LogService";
940 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800941 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700942 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
943 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
944 asyncResp->res.jsonValue["Id"] = "CPU Log";
945 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
946 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -0800947 asyncResp->res.jsonValue["Entries"] = {
948 {"@odata.id",
Jason M. Billsf9fc2df2019-02-26 16:00:37 -0800949 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -0700950 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700951 {"Oem",
952 {{"#CpuLog.Immediate",
Jason M. Billsd53dd412019-02-12 17:16:22 -0800953 {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/"
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800954 "Actions/Oem/CpuLog.Immediate"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700955
956#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700957 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700958 {"#CpuLog.SendRawPeci",
Jason M. Billsd53dd412019-02-12 17:16:22 -0800959 {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/"
960 "Actions/Oem/CpuLog.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700961#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700962 }
963};
964
Jason M. Billse1f26342018-07-18 12:12:00 -0700965class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700966{
967 public:
968 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700969 CPULogEntryCollection(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -0800970 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700971 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700972 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700973 {boost::beast::http::verb::get, {{"Login"}}},
974 {boost::beast::http::verb::head, {{"Login"}}},
975 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
976 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
977 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
978 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700979 }
980
981 private:
982 /**
983 * Functions triggers appropriate requests on DBus
984 */
985 void doGet(crow::Response &res, const crow::Request &req,
986 const std::vector<std::string> &params) override
987 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700988 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700989 // Collections don't include the static data added by SubRoute because
990 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700991 auto getLogEntriesCallback = [asyncResp](
992 const boost::system::error_code ec,
993 const std::vector<std::string> &resp) {
994 if (ec)
995 {
996 if (ec.value() !=
997 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -0700998 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700999 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1000 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001001 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001002 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001003 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001004 }
1005 asyncResp->res.jsonValue["@odata.type"] =
1006 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001007 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsd53dd412019-02-12 17:16:22 -08001008 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001009 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsd53dd412019-02-12 17:16:22 -08001010 "/redfish/v1/"
1011 "$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001012 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1013 asyncResp->res.jsonValue["Description"] =
1014 "Collection of CPU Log Entries";
1015 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1016 logEntryArray = nlohmann::json::array();
1017 for (const std::string &objpath : resp)
1018 {
1019 // Don't list the immediate log
1020 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001021 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001022 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001023 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001024 std::size_t lastPos = objpath.rfind("/");
1025 if (lastPos != std::string::npos)
1026 {
1027 logEntryArray.push_back(
Jason M. Billsd53dd412019-02-12 17:16:22 -08001028 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001029 "CpuLog/Entries/" +
1030 objpath.substr(lastPos + 1)}});
1031 }
1032 }
1033 asyncResp->res.jsonValue["Members@odata.count"] =
1034 logEntryArray.size();
1035 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001036 crow::connections::systemBus->async_method_call(
1037 std::move(getLogEntriesCallback),
1038 "xyz.openbmc_project.ObjectMapper",
1039 "/xyz/openbmc_project/object_mapper",
1040 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001041 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001042 }
1043};
1044
1045std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1046{
Jason M. Billsc4d00432019-02-12 17:17:48 -08001047 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data");
1048 if (cdIt != cpuLog.end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001049 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001050 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1051 if (siIt != cdIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001052 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001053 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1054 if (tsIt != siIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001055 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001056 const std::string *logTime =
1057 tsIt->get_ptr<const std::string *>();
1058 if (logTime != nullptr)
1059 {
1060 return *logTime;
1061 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001062 }
1063 }
1064 }
1065 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1066
1067 return std::string();
1068}
1069
Jason M. Billse1f26342018-07-18 12:12:00 -07001070class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001071{
1072 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001073 CPULogEntry(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001074 Node(app,
1075 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001076 std::string())
1077 {
1078 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001079 {boost::beast::http::verb::get, {{"Login"}}},
1080 {boost::beast::http::verb::head, {{"Login"}}},
1081 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1082 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1083 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1084 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001085 }
1086
1087 private:
1088 void doGet(crow::Response &res, const crow::Request &req,
1089 const std::vector<std::string> &params) override
1090 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001091 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001092 if (params.size() != 1)
1093 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001094 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001095 return;
1096 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001097 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001098 auto getStoredLogCallback = [asyncResp, logId](
1099 const boost::system::error_code ec,
1100 const std::variant<std::string> &resp) {
1101 if (ec)
1102 {
1103 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1104 messages::internalError(asyncResp->res);
1105 return;
1106 }
1107 const std::string *log = std::get_if<std::string>(&resp);
1108 if (log == nullptr)
1109 {
1110 messages::internalError(asyncResp->res);
1111 return;
1112 }
1113 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1114 if (j.is_discarded())
1115 {
1116 messages::internalError(asyncResp->res);
1117 return;
1118 }
1119 std::string t = getLogCreatedTime(j);
1120 asyncResp->res.jsonValue = {
1121 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1122 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1123 {"@odata.id",
Jason M. Billsd53dd412019-02-12 17:16:22 -08001124 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/" +
Ed Tanousabf2add2019-01-22 16:40:12 -08001125 std::to_string(logId)},
1126 {"Name", "CPU Debug Log"},
1127 {"Id", logId},
1128 {"EntryType", "Oem"},
1129 {"OemRecordFormat", "Intel CPU Log"},
1130 {"Oem", {{"Intel", std::move(j)}}},
1131 {"Created", std::move(t)}};
1132 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001133 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001134 std::move(getStoredLogCallback), cpuLogObject,
1135 cpuLogPath + std::string("/") + std::to_string(logId),
1136 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001137 }
1138};
1139
Jason M. Billse1f26342018-07-18 12:12:00 -07001140class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001141{
1142 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001143 ImmediateCPULog(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001144 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001145 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001146 {
1147 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001148 {boost::beast::http::verb::get, {{"Login"}}},
1149 {boost::beast::http::verb::head, {{"Login"}}},
1150 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1151 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1152 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1153 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001154 }
1155
1156 private:
1157 void doPost(crow::Response &res, const crow::Request &req,
1158 const std::vector<std::string> &params) override
1159 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001160 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001161 static std::unique_ptr<sdbusplus::bus::match::match>
1162 immediateLogMatcher;
1163
1164 // Only allow one Immediate Log request at a time
1165 if (immediateLogMatcher != nullptr)
1166 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001167 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001168 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001169 return;
1170 }
1171 // Make this static so it survives outside this method
1172 static boost::asio::deadline_timer timeout(*req.ioService);
1173
1174 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001175 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001176 immediateLogMatcher = nullptr;
1177 if (ec)
1178 {
1179 // operation_aborted is expected if timer is canceled before
1180 // completion.
1181 if (ec != boost::asio::error::operation_aborted)
1182 {
1183 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1184 }
1185 return;
1186 }
1187 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1188
Jason M. Billsf12894f2018-10-09 12:45:45 -07001189 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001190 });
1191
Jason M. Billse1f26342018-07-18 12:12:00 -07001192 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001193 sdbusplus::message::message &m) {
1194 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1195 boost::system::error_code ec;
1196 timeout.cancel(ec);
1197 if (ec)
1198 {
1199 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1200 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001201 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001202 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001203 std::string, boost::container::flat_map<
1204 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001205 interfacesAdded;
1206 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001207 const std::string *log = std::get_if<std::string>(
1208 &interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001209 if (log == nullptr)
1210 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001211 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001212 // Careful with immediateLogMatcher. It is a unique_ptr to the
1213 // match object inside which this lambda is executing. Once it
1214 // is set to nullptr, the match object will be destroyed and the
1215 // lambda will lose its context, including res, so it needs to
1216 // be the last thing done.
1217 immediateLogMatcher = nullptr;
1218 return;
1219 }
1220 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1221 if (j.is_discarded())
1222 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001223 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001224 // Careful with immediateLogMatcher. It is a unique_ptr to the
1225 // match object inside which this lambda is executing. Once it
1226 // is set to nullptr, the match object will be destroyed and the
1227 // lambda will lose its context, including res, so it needs to
1228 // be the last thing done.
1229 immediateLogMatcher = nullptr;
1230 return;
1231 }
1232 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001233 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001234 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1235 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1236 {"Name", "CPU Debug Log"},
1237 {"EntryType", "Oem"},
1238 {"OemRecordFormat", "Intel CPU Log"},
1239 {"Oem", {{"Intel", std::move(j)}}},
1240 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001241 // Careful with immediateLogMatcher. It is a unique_ptr to the
1242 // match object inside which this lambda is executing. Once it is
1243 // set to nullptr, the match object will be destroyed and the lambda
1244 // will lose its context, including res, so it needs to be the last
1245 // thing done.
1246 immediateLogMatcher = nullptr;
1247 };
1248 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1249 *crow::connections::systemBus,
1250 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001251 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001252 std::move(immediateLogMatcherCallback));
1253
1254 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001255 [asyncResp](const boost::system::error_code ec,
1256 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001257 if (ec)
1258 {
1259 if (ec.value() ==
1260 boost::system::errc::operation_not_supported)
1261 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001262 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001263 }
1264 else
1265 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001266 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001267 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001268 boost::system::error_code timeoutec;
1269 timeout.cancel(timeoutec);
1270 if (timeoutec)
1271 {
1272 BMCWEB_LOG_ERROR << "error canceling timer "
1273 << timeoutec;
1274 }
1275 immediateLogMatcher = nullptr;
1276 return;
1277 }
1278 };
1279 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001280 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1281 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001282 }
1283};
1284
Jason M. Billse1f26342018-07-18 12:12:00 -07001285class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001286{
1287 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001288 SendRawPECI(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001289 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001290 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001291 {
1292 entityPrivileges = {
1293 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1294 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1295 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1296 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1297 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1298 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1299 }
1300
1301 private:
1302 void doPost(crow::Response &res, const crow::Request &req,
1303 const std::vector<std::string> &params) override
1304 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001305 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001306 uint8_t clientAddress = 0;
1307 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001308 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001309 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1310 "ReadLength", readLength, "PECICommand",
1311 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001312 {
Ed Tanousb1556422018-10-16 14:09:17 -07001313 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001314 }
Ed Tanousb1556422018-10-16 14:09:17 -07001315
Ed Tanous1da66f72018-07-27 16:13:37 -07001316 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001317 auto sendRawPECICallback =
1318 [asyncResp](const boost::system::error_code ec,
1319 const std::vector<uint8_t> &resp) {
1320 if (ec)
1321 {
1322 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1323 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001324 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001325 return;
1326 }
1327 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1328 {"PECIResponse", resp}};
1329 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001330 // Call the SendRawPECI command with the provided data
1331 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001332 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1333 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001334 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001335 }
1336};
1337
1338} // namespace redfish