blob: 562bbfcc9310abff712754f5b4e9d5262d9c8c81 [file] [log] [blame]
Ed Tanous1da66f72018-07-27 16:13:37 -07001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17
18#include "node.hpp"
19
Jason M. Billse1f26342018-07-18 12:12:00 -070020#include <systemd/sd-journal.h>
21
Ed Tanous1da66f72018-07-27 16:13:37 -070022#include <boost/container/flat_map.hpp>
Jason M. Billse1f26342018-07-18 12:12:00 -070023#include <boost/utility/string_view.hpp>
Ed Tanous1da66f72018-07-27 16:13:37 -070024#include <experimental/filesystem>
25
26namespace redfish
27{
28
Ed Tanous4ed77cd2018-10-15 08:08:07 -070029constexpr char const *cpuLogObject = "com.intel.CpuDebugLog";
30constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog";
31constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate";
32constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
33constexpr char const *cpuLogImmediateInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070034 "com.intel.CpuDebugLog.Immediate";
Jason M. Billse1f26342018-07-18 12:12:00 -070035constexpr char const *cpuLogRawPECIInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070036 "com.intel.CpuDebugLog.SendRawPeci";
37
38namespace fs = std::experimental::filesystem;
39
40class LogServiceCollection : public Node
41{
42 public:
43 template <typename CrowApp>
44 LogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -070045 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -070046 {
47 // Collections use static ID for SubRoute to add to its parent, but only
48 // load dynamic data so the duplicate static members don't get displayed
Ed Tanous4ed77cd2018-10-15 08:08:07 -070049 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
Ed Tanous1da66f72018-07-27 16:13:37 -070050 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -070051 {boost::beast::http::verb::get, {{"Login"}}},
52 {boost::beast::http::verb::head, {{"Login"}}},
53 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
54 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
55 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
56 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -070057 }
58
59 private:
60 /**
61 * Functions triggers appropriate requests on DBus
62 */
63 void doGet(crow::Response &res, const crow::Request &req,
64 const std::vector<std::string> &params) override
65 {
Jason M. Billse1f26342018-07-18 12:12:00 -070066 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -070067 // Collections don't include the static data added by SubRoute because
68 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -070069 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -070070 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -070071 asyncResp->res.jsonValue["@odata.context"] =
Ed Tanous1da66f72018-07-27 16:13:37 -070072 "/redfish/v1/"
73 "$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -070074 asyncResp->res.jsonValue["@odata.id"] =
75 "/redfish/v1/Managers/bmc/LogServices";
76 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
77 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -070078 "Collection of LogServices for this Manager";
Jason M. Billse1f26342018-07-18 12:12:00 -070079 nlohmann::json &logserviceArray = asyncResp->res.jsonValue["Members"];
Ed Tanous1da66f72018-07-27 16:13:37 -070080 logserviceArray = nlohmann::json::array();
Jason M. Billse1f26342018-07-18 12:12:00 -070081 logserviceArray.push_back(
82 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -070083#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
84 logserviceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -070085 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -070086#endif
Jason M. Billse1f26342018-07-18 12:12:00 -070087 asyncResp->res.jsonValue["Members@odata.count"] =
88 logserviceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -070089 }
90};
91
Jason M. Billse1f26342018-07-18 12:12:00 -070092class BMCLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -070093{
94 public:
95 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -070096 BMCLogService(CrowApp &app) :
97 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/")
98 {
99 // Set the id for SubRoute
100 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog";
101 entityPrivileges = {
102 {boost::beast::http::verb::get, {{"Login"}}},
103 {boost::beast::http::verb::head, {{"Login"}}},
104 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
105 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
106 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
107 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
108 }
109
110 private:
111 void doGet(crow::Response &res, const crow::Request &req,
112 const std::vector<std::string> &params) override
113 {
114 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
115 // Copy over the static data to include the entries added by SubRoute
116 asyncResp->res.jsonValue = Node::json;
117 asyncResp->res.jsonValue["@odata.type"] =
118 "#LogService.v1_1_0.LogService";
119 asyncResp->res.jsonValue["@odata.context"] =
120 "/redfish/v1/$metadata#LogService.LogService";
121 asyncResp->res.jsonValue["Name"] = "Open BMC Log Service";
122 asyncResp->res.jsonValue["Description"] = "BMC Log Service";
123 asyncResp->res.jsonValue["Id"] = "BMC Log";
124 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
125 }
126};
127
128static int fillBMCLogEntryJson(const std::string &bmcLogEntryID,
129 sd_journal *journal,
130 nlohmann::json &bmcLogEntryJson)
131{
132 // Get the Log Entry contents
133 int ret = 0;
134 const char *data = nullptr;
135 size_t length = 0;
136
137 ret =
138 sd_journal_get_data(journal, "MESSAGE", (const void **)&data, &length);
139 if (ret < 0)
140 {
141 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
142 return 1;
143 }
144 boost::string_view msg;
145 msg = boost::string_view(data, length);
146 // Only use the content after the "=" character.
147 msg.remove_prefix(std::min(msg.find("=") + 1, msg.size()));
148
149 // Get the severity from the PRIORITY field
150 boost::string_view priority;
151 int severity = 8; // Default to an invalid priority
152 ret =
153 sd_journal_get_data(journal, "PRIORITY", (const void **)&data, &length);
154 if (ret < 0)
155 {
156 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
157 return 1;
158 }
159 priority = boost::string_view(data, length);
160 // Check length for sanity. Must be a single digit in the form
161 // "PRIORITY=[0-7]"
162 if (priority.size() > sizeof("PRIORITY=0"))
163 {
164 BMCWEB_LOG_ERROR << "Invalid PRIORITY field length";
165 return 1;
166 }
167 // Only use the content after the "=" character.
168 priority.remove_prefix(std::min(priority.find("=") + 1, priority.size()));
169 severity = strtol(priority.data(), nullptr, 10);
170
171 // Get the Created time from the timestamp
172 // Get the entry timestamp
173 uint64_t timestamp = 0;
174 ret = sd_journal_get_realtime_usec(journal, &timestamp);
175 if (ret < 0)
176 {
177 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
178 << strerror(-ret);
179 }
180 time_t t =
181 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
182 struct tm *loctime = localtime(&t);
183 char entryTime[64] = {};
184 if (NULL != loctime)
185 {
186 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
187 }
188 // Insert the ':' into the timezone
189 boost::string_view t1(entryTime);
190 boost::string_view t2(entryTime);
191 if (t1.size() > 2 && t2.size() > 2)
192 {
193 t1.remove_suffix(2);
194 t2.remove_prefix(t2.size() - 2);
195 }
196 const std::string entryTimeStr(t1.to_string() + ":" + t2.to_string());
197
198 // Fill in the log entry with the gathered data
199 bmcLogEntryJson = {
200 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
201 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
202 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/" +
203 bmcLogEntryID},
204 {"Name", "BMC Journal Entry"},
205 {"Id", bmcLogEntryID},
206 {"Message", msg.to_string()},
207 {"EntryType", "Oem"},
208 {"Severity",
209 severity <= 2 ? "Critical"
210 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
211 {"OemRecordFormat", "Intel BMC Journal Entry"},
212 {"Created", std::move(entryTimeStr)}};
213 return 0;
214}
215
216class BMCLogEntryCollection : public Node
217{
218 public:
219 template <typename CrowApp>
220 BMCLogEntryCollection(CrowApp &app) :
221 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/")
222 {
223 // Collections use static ID for SubRoute to add to its parent, but only
224 // load dynamic data so the duplicate static members don't get displayed
225 Node::json["@odata.id"] =
226 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
227 entityPrivileges = {
228 {boost::beast::http::verb::get, {{"Login"}}},
229 {boost::beast::http::verb::head, {{"Login"}}},
230 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
231 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
232 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
233 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
234 }
235
236 private:
237 void doGet(crow::Response &res, const crow::Request &req,
238 const std::vector<std::string> &params) override
239 {
240 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
241 // Collections don't include the static data added by SubRoute because
242 // it has a duplicate entry for members
243 asyncResp->res.jsonValue["@odata.type"] =
244 "#LogEntryCollection.LogEntryCollection";
245 asyncResp->res.jsonValue["@odata.context"] =
246 "/redfish/v1/"
247 "$metadata#LogEntryCollection.LogEntryCollection";
248 asyncResp->res.jsonValue["@odata.id"] =
249 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
250 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
251 asyncResp->res.jsonValue["Description"] =
252 "Collection of BMC Journal Entries";
253 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
254 logEntryArray = nlohmann::json::array();
255
256 // Go through the journal and use the timestamp to create a unique ID
257 // for each entry
258 sd_journal *journalTmp = nullptr;
259 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
260 if (ret < 0)
261 {
262 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700263 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700264 return;
265 }
266 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
267 journalTmp, sd_journal_close);
268 journalTmp = nullptr;
269 uint64_t prevTs = 0;
270 int index = 0;
271 SD_JOURNAL_FOREACH(journal.get())
272 {
273 // Get the entry timestamp
274 uint64_t curTs = 0;
275 ret = sd_journal_get_realtime_usec(journal.get(), &curTs);
276 if (ret < 0)
277 {
278 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
279 << strerror(-ret);
280 continue;
281 }
282 // If the timestamp isn't unique, increment the index
283 if (curTs == prevTs)
284 {
285 index++;
286 }
287 else
288 {
289 // Otherwise, reset it
290 index = 0;
291 }
292 // Save the timestamp
293 prevTs = curTs;
294
295 std::string idStr(std::to_string(curTs));
296 if (index > 0)
297 {
298 idStr += "_" + std::to_string(index);
299 }
300 logEntryArray.push_back({});
301 nlohmann::json &bmcLogEntry = logEntryArray.back();
302 if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0)
303 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700304 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700305 return;
306 }
307 }
308 asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size();
309 }
310};
311
312class BMCLogEntry : public Node
313{
314 public:
315 BMCLogEntry(CrowApp &app) :
316 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/",
317 std::string())
318 {
319 entityPrivileges = {
320 {boost::beast::http::verb::get, {{"Login"}}},
321 {boost::beast::http::verb::head, {{"Login"}}},
322 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
323 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
324 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
325 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
326 }
327
328 private:
329 void doGet(crow::Response &res, const crow::Request &req,
330 const std::vector<std::string> &params) override
331 {
332 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
333 if (params.size() != 1)
334 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700335 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700336 return;
337 }
338 // Convert the unique ID back to a timestamp to find the entry
339 boost::string_view tsStr(params[0]);
340 boost::string_view indexStr(params[0]);
341 uint64_t ts = 0;
342 uint16_t index = 0;
343 auto underscorePos = tsStr.find("_");
344 if (underscorePos == tsStr.npos)
345 {
346 // Timestamp has no index
347 ts = strtoull(tsStr.data(), nullptr, 10);
348 }
349 else
350 {
351 // Timestamp has an index
352 tsStr.remove_suffix(tsStr.size() - underscorePos + 1);
353 ts = strtoull(tsStr.data(), nullptr, 10);
354 indexStr.remove_prefix(underscorePos + 1);
355 index =
356 static_cast<uint16_t>(strtoul(indexStr.data(), nullptr, 10));
357 }
358
359 sd_journal *journalTmp = nullptr;
360 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
361 if (ret < 0)
362 {
363 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700364 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700365 return;
366 }
367 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
368 journalTmp, sd_journal_close);
369 journalTmp = nullptr;
370 // Go to the timestamp in the log and move to the entry at the index
371 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
372 for (int i = 0; i <= index; i++)
373 {
374 sd_journal_next(journal.get());
375 }
376 if (fillBMCLogEntryJson(params[0], journal.get(),
377 asyncResp->res.jsonValue) != 0)
378 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700379 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700380 return;
381 }
382 }
383};
384
385class CPULogService : public Node
386{
387 public:
388 template <typename CrowApp>
389 CPULogService(CrowApp &app) :
390 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700391 {
392 // Set the id for SubRoute
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700393 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Ed Tanous1da66f72018-07-27 16:13:37 -0700394 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700395 {boost::beast::http::verb::get, {{"Login"}}},
396 {boost::beast::http::verb::head, {{"Login"}}},
397 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
398 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
399 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
400 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700401 }
402
403 private:
404 /**
405 * Functions triggers appropriate requests on DBus
406 */
407 void doGet(crow::Response &res, const crow::Request &req,
408 const std::vector<std::string> &params) override
409 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700410 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700411 // Copy over the static data to include the entries added by SubRoute
Jason M. Billse1f26342018-07-18 12:12:00 -0700412 asyncResp->res.jsonValue = Node::json;
413 asyncResp->res.jsonValue["@odata.type"] =
414 "#LogService.v1_1_0.LogService";
415 asyncResp->res.jsonValue["@odata.context"] =
416 "/redfish/v1/"
417 "$metadata#LogService.LogService";
418 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
419 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
420 asyncResp->res.jsonValue["Id"] = "CPU Log";
421 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
422 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
423 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700424 {"Oem",
425 {{"#CpuLog.Immediate",
426 {{"target",
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700427 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Ed Tanous1da66f72018-07-27 16:13:37 -0700428 "CpuLog.Immediate"}}}}}};
429
430#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700431 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700432 {"#CpuLog.SendRawPeci",
433 {{"target",
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700434 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Ed Tanous1da66f72018-07-27 16:13:37 -0700435 "CpuLog.SendRawPeci"}}});
436#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700437 }
438};
439
Jason M. Billse1f26342018-07-18 12:12:00 -0700440class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700441{
442 public:
443 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700444 CPULogEntryCollection(CrowApp &app) :
445 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700446 {
447 // Collections use static ID for SubRoute to add to its parent, but only
448 // load dynamic data so the duplicate static members don't get displayed
449 Node::json["@odata.id"] =
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700450 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
Ed Tanous1da66f72018-07-27 16:13:37 -0700451 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700452 {boost::beast::http::verb::get, {{"Login"}}},
453 {boost::beast::http::verb::head, {{"Login"}}},
454 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
455 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
456 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
457 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700458 }
459
460 private:
461 /**
462 * Functions triggers appropriate requests on DBus
463 */
464 void doGet(crow::Response &res, const crow::Request &req,
465 const std::vector<std::string> &params) override
466 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700467 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700468 // Collections don't include the static data added by SubRoute because
469 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700470 auto getLogEntriesCallback = [asyncResp](
471 const boost::system::error_code ec,
472 const std::vector<std::string> &resp) {
473 if (ec)
474 {
475 if (ec.value() !=
476 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -0700477 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700478 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
479 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -0700480 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700481 return;
Ed Tanous1da66f72018-07-27 16:13:37 -0700482 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700483 }
484 asyncResp->res.jsonValue["@odata.type"] =
485 "#LogEntryCollection.LogEntryCollection";
486 asyncResp->res.jsonValue["@odata.context"] =
487 "/redfish/v1/"
488 "$metadata#LogEntryCollection.LogEntryCollection";
489 asyncResp->res.jsonValue["@odata.id"] =
490 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
491 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
492 asyncResp->res.jsonValue["Description"] =
493 "Collection of CPU Log Entries";
494 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
495 logEntryArray = nlohmann::json::array();
496 for (const std::string &objpath : resp)
497 {
498 // Don't list the immediate log
499 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -0700500 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700501 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -0700502 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700503 std::size_t lastPos = objpath.rfind("/");
504 if (lastPos != std::string::npos)
505 {
506 logEntryArray.push_back(
507 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
508 "CpuLog/Entries/" +
509 objpath.substr(lastPos + 1)}});
510 }
511 }
512 asyncResp->res.jsonValue["Members@odata.count"] =
513 logEntryArray.size();
514 };
Ed Tanous1da66f72018-07-27 16:13:37 -0700515 crow::connections::systemBus->async_method_call(
516 std::move(getLogEntriesCallback),
517 "xyz.openbmc_project.ObjectMapper",
518 "/xyz/openbmc_project/object_mapper",
519 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700520 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -0700521 }
522};
523
524std::string getLogCreatedTime(const nlohmann::json &cpuLog)
525{
526 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
527 if (metaIt != cpuLog.end())
528 {
529 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
530 if (tsIt != metaIt->end())
531 {
532 const std::string *logTime = tsIt->get_ptr<const std::string *>();
533 if (logTime != nullptr)
534 {
535 return *logTime;
536 }
537 }
538 }
539 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
540
541 return std::string();
542}
543
Jason M. Billse1f26342018-07-18 12:12:00 -0700544class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700545{
546 public:
Jason M. Billse1f26342018-07-18 12:12:00 -0700547 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700548 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -0700549 std::string())
550 {
551 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700552 {boost::beast::http::verb::get, {{"Login"}}},
553 {boost::beast::http::verb::head, {{"Login"}}},
554 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
555 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
556 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
557 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700558 }
559
560 private:
561 void doGet(crow::Response &res, const crow::Request &req,
562 const std::vector<std::string> &params) override
563 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700564 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700565 if (params.size() != 1)
566 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700567 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700568 return;
569 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700570 const uint8_t logId = std::atoi(params[0].c_str());
Jason M. Billse1f26342018-07-18 12:12:00 -0700571 auto getStoredLogCallback =
572 [asyncResp,
573 logId](const boost::system::error_code ec,
574 const sdbusplus::message::variant<std::string> &resp) {
575 if (ec)
576 {
577 BMCWEB_LOG_DEBUG << "failed to get log ec: "
578 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -0700579 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700580 return;
581 }
582 const std::string *log =
583 mapbox::getPtr<const std::string>(resp);
584 if (log == nullptr)
585 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700586 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700587 return;
588 }
589 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
590 if (j.is_discarded())
591 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700592 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700593 return;
594 }
595 std::string t = getLogCreatedTime(j);
596 asyncResp->res.jsonValue = {
597 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
598 {"@odata.context",
599 "/redfish/v1/$metadata#LogEntry.LogEntry"},
600 {"@odata.id",
601 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
602 std::to_string(logId)},
603 {"Name", "CPU Debug Log"},
604 {"Id", logId},
605 {"EntryType", "Oem"},
606 {"OemRecordFormat", "Intel CPU Log"},
607 {"Oem", {{"Intel", std::move(j)}}},
608 {"Created", std::move(t)}};
609 };
Ed Tanous1da66f72018-07-27 16:13:37 -0700610 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700611 std::move(getStoredLogCallback), cpuLogObject,
612 cpuLogPath + std::string("/") + std::to_string(logId),
613 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -0700614 }
615};
616
Jason M. Billse1f26342018-07-18 12:12:00 -0700617class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700618{
619 public:
Jason M. Billse1f26342018-07-18 12:12:00 -0700620 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700621 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -0700622 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700623 {
624 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700625 {boost::beast::http::verb::get, {{"Login"}}},
626 {boost::beast::http::verb::head, {{"Login"}}},
627 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
628 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
629 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
630 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700631 }
632
633 private:
634 void doPost(crow::Response &res, const crow::Request &req,
635 const std::vector<std::string> &params) override
636 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700637 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700638 static std::unique_ptr<sdbusplus::bus::match::match>
639 immediateLogMatcher;
640
641 // Only allow one Immediate Log request at a time
642 if (immediateLogMatcher != nullptr)
643 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700644 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -0700645 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -0700646 return;
647 }
648 // Make this static so it survives outside this method
649 static boost::asio::deadline_timer timeout(*req.ioService);
650
651 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -0700652 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -0700653 immediateLogMatcher = nullptr;
654 if (ec)
655 {
656 // operation_aborted is expected if timer is canceled before
657 // completion.
658 if (ec != boost::asio::error::operation_aborted)
659 {
660 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
661 }
662 return;
663 }
664 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
665
Jason M. Billsf12894f2018-10-09 12:45:45 -0700666 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700667 });
668
Jason M. Billse1f26342018-07-18 12:12:00 -0700669 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -0700670 sdbusplus::message::message &m) {
671 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
672 boost::system::error_code ec;
673 timeout.cancel(ec);
674 if (ec)
675 {
676 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
677 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700678 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -0700679 boost::container::flat_map<
680 std::string,
681 boost::container::flat_map<
682 std::string, sdbusplus::message::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700683 interfacesAdded;
684 m.read(objPath, interfacesAdded);
Ed Tanous1da66f72018-07-27 16:13:37 -0700685 const std::string *log = mapbox::getPtr<const std::string>(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700686 interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -0700687 if (log == nullptr)
688 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700689 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700690 // Careful with immediateLogMatcher. It is a unique_ptr to the
691 // match object inside which this lambda is executing. Once it
692 // is set to nullptr, the match object will be destroyed and the
693 // lambda will lose its context, including res, so it needs to
694 // be the last thing done.
695 immediateLogMatcher = nullptr;
696 return;
697 }
698 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
699 if (j.is_discarded())
700 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700701 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700702 // Careful with immediateLogMatcher. It is a unique_ptr to the
703 // match object inside which this lambda is executing. Once it
704 // is set to nullptr, the match object will be destroyed and the
705 // lambda will lose its context, including res, so it needs to
706 // be the last thing done.
707 immediateLogMatcher = nullptr;
708 return;
709 }
710 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -0700711 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700712 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
713 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
714 {"Name", "CPU Debug Log"},
715 {"EntryType", "Oem"},
716 {"OemRecordFormat", "Intel CPU Log"},
717 {"Oem", {{"Intel", std::move(j)}}},
718 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700719 // Careful with immediateLogMatcher. It is a unique_ptr to the
720 // match object inside which this lambda is executing. Once it is
721 // set to nullptr, the match object will be destroyed and the lambda
722 // will lose its context, including res, so it needs to be the last
723 // thing done.
724 immediateLogMatcher = nullptr;
725 };
726 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
727 *crow::connections::systemBus,
728 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700729 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -0700730 std::move(immediateLogMatcherCallback));
731
732 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -0700733 [asyncResp](const boost::system::error_code ec,
734 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -0700735 if (ec)
736 {
737 if (ec.value() ==
738 boost::system::errc::operation_not_supported)
739 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700740 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700741 }
742 else
743 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700744 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700745 }
Ed Tanous1da66f72018-07-27 16:13:37 -0700746 boost::system::error_code timeoutec;
747 timeout.cancel(timeoutec);
748 if (timeoutec)
749 {
750 BMCWEB_LOG_ERROR << "error canceling timer "
751 << timeoutec;
752 }
753 immediateLogMatcher = nullptr;
754 return;
755 }
756 };
757 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700758 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
759 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -0700760 }
761};
762
Jason M. Billse1f26342018-07-18 12:12:00 -0700763class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700764{
765 public:
Jason M. Billse1f26342018-07-18 12:12:00 -0700766 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700767 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -0700768 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700769 {
770 entityPrivileges = {
771 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
772 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
773 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
774 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
775 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
776 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
777 }
778
779 private:
780 void doPost(crow::Response &res, const crow::Request &req,
781 const std::vector<std::string> &params) override
782 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700783 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700784 // Get the Raw PECI command from the request
Jason M. Billse1f26342018-07-18 12:12:00 -0700785 nlohmann::json rawPECICmd;
786 if (!json_util::processJsonFromRequest(res, req, rawPECICmd))
Ed Tanous1da66f72018-07-27 16:13:37 -0700787 {
788 return;
789 }
790 // Get the Client Address from the request
Jason M. Billse1f26342018-07-18 12:12:00 -0700791 nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress");
792 if (caIt == rawPECICmd.end())
Ed Tanous1da66f72018-07-27 16:13:37 -0700793 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700794 messages::propertyMissing(asyncResp->res, "ClientAddress",
795 "/ClientAddress");
Ed Tanous1da66f72018-07-27 16:13:37 -0700796 return;
797 }
798 const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
799 if (ca == nullptr)
800 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700801 messages::propertyValueTypeError(asyncResp->res, caIt->dump(),
802 "ClientAddress", "/ClientAddress");
Ed Tanous1da66f72018-07-27 16:13:37 -0700803 return;
804 }
805 // Get the Read Length from the request
806 const uint8_t clientAddress = static_cast<uint8_t>(*ca);
Jason M. Billse1f26342018-07-18 12:12:00 -0700807 nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength");
808 if (rlIt == rawPECICmd.end())
Ed Tanous1da66f72018-07-27 16:13:37 -0700809 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700810 messages::propertyMissing(asyncResp->res, "ReadLength",
811 "/ReadLength");
Ed Tanous1da66f72018-07-27 16:13:37 -0700812 return;
813 }
814 const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
815 if (rl == nullptr)
816 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700817 messages::propertyValueTypeError(asyncResp->res, rlIt->dump(),
818 "ReadLength", "/ReadLength");
Ed Tanous1da66f72018-07-27 16:13:37 -0700819 return;
820 }
821 // Get the PECI Command from the request
822 const uint32_t readLength = static_cast<uint32_t>(*rl);
Jason M. Billse1f26342018-07-18 12:12:00 -0700823 nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand");
824 if (pcIt == rawPECICmd.end())
Ed Tanous1da66f72018-07-27 16:13:37 -0700825 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700826 messages::propertyMissing(asyncResp->res, "PECICommand",
827 "/PECICommand");
Ed Tanous1da66f72018-07-27 16:13:37 -0700828 return;
829 }
830 std::vector<uint8_t> peciCommand;
831 for (auto pc : *pcIt)
832 {
833 const uint64_t *val = pc.get_ptr<const uint64_t *>();
834 if (val == nullptr)
835 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700836 messages::propertyValueTypeError(
837 asyncResp->res, pc.dump(),
838 "PECICommand/" + std::to_string(peciCommand.size()),
Ed Tanous1da66f72018-07-27 16:13:37 -0700839 "/PECICommand");
Ed Tanous1da66f72018-07-27 16:13:37 -0700840 return;
841 }
842 peciCommand.push_back(static_cast<uint8_t>(*val));
843 }
844 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -0700845 auto sendRawPECICallback =
846 [asyncResp](const boost::system::error_code ec,
847 const std::vector<uint8_t> &resp) {
848 if (ec)
849 {
850 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
851 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -0700852 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700853 return;
854 }
855 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
856 {"PECIResponse", resp}};
857 };
Ed Tanous1da66f72018-07-27 16:13:37 -0700858 // Call the SendRawPECI command with the provided data
859 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -0700860 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
861 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700862 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -0700863 }
864};
865
866} // namespace redfish