xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision 4ed77cd5cfcef505b2e33cd571d92d3dc6caac3a)
1 /*
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 
20 #include <boost/container/flat_map.hpp>
21 #include <experimental/filesystem>
22 
23 namespace redfish
24 {
25 
26 constexpr char const *cpuLogObject = "com.intel.CpuDebugLog";
27 constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog";
28 constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate";
29 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
30 constexpr char const *cpuLogImmediateInterface =
31     "com.intel.CpuDebugLog.Immediate";
32 constexpr char const *cpuLogRawPeciInterface =
33     "com.intel.CpuDebugLog.SendRawPeci";
34 
35 namespace fs = std::experimental::filesystem;
36 
37 class LogServiceCollection : public Node
38 {
39   public:
40     template <typename CrowApp>
41     LogServiceCollection(CrowApp &app) :
42         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
43     {
44         // Collections use static ID for SubRoute to add to its parent, but only
45         // load dynamic data so the duplicate static members don't get displayed
46         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
47         entityPrivileges = {
48             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
49             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
50             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
51             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
52             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
53             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
54     }
55 
56   private:
57     /**
58      * Functions triggers appropriate requests on DBus
59      */
60     void doGet(crow::Response &res, const crow::Request &req,
61                const std::vector<std::string> &params) override
62     {
63         // Collections don't include the static data added by SubRoute because
64         // it has a duplicate entry for members
65         res.jsonValue["@odata.type"] =
66             "#LogServiceCollection.LogServiceCollection";
67         res.jsonValue["@odata.context"] =
68             "/redfish/v1/"
69             "$metadata#LogServiceCollection.LogServiceCollection";
70         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
71         res.jsonValue["Name"] = "Open BMC Log Services Collection";
72         res.jsonValue["Description"] =
73             "Collection of LogServices for this Manager";
74         nlohmann::json &logserviceArray = res.jsonValue["Members"];
75         logserviceArray = nlohmann::json::array();
76 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
77         logserviceArray.push_back(
78             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
79 #endif
80         res.jsonValue["Members@odata.count"] = logserviceArray.size();
81         res.end();
82     }
83 };
84 
85 class CpuLogService : public Node
86 {
87   public:
88     template <typename CrowApp>
89     CpuLogService(CrowApp &app) :
90         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog")
91     {
92         // Set the id for SubRoute
93         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
94         entityPrivileges = {
95             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
96             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
97             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
98             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
99             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
100             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
101     }
102 
103   private:
104     /**
105      * Functions triggers appropriate requests on DBus
106      */
107     void doGet(crow::Response &res, const crow::Request &req,
108                const std::vector<std::string> &params) override
109     {
110         // Copy over the static data to include the entries added by SubRoute
111         res.jsonValue = Node::json;
112         res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
113         res.jsonValue["@odata.context"] = "/redfish/v1/"
114                                           "$metadata#LogService.LogService";
115         res.jsonValue["Name"] = "Open BMC CPU Log Service";
116         res.jsonValue["Description"] = "CPU Log Service";
117         res.jsonValue["Id"] = "CPU Log";
118         res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
119         res.jsonValue["MaxNumberOfRecords"] = 3;
120         res.jsonValue["Actions"] = {
121             {"Oem",
122              {{"#CpuLog.Immediate",
123                {{"target",
124                  "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
125                  "CpuLog.Immediate"}}}}}};
126 
127 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
128         res.jsonValue["Actions"]["Oem"].push_back(
129             {"#CpuLog.SendRawPeci",
130              {{"target",
131                "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
132                "CpuLog.SendRawPeci"}}});
133 #endif
134         res.end();
135     }
136 };
137 
138 class CpuLogEntryCollection : public Node
139 {
140   public:
141     template <typename CrowApp>
142     CpuLogEntryCollection(CrowApp &app) :
143         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries")
144     {
145         // Collections use static ID for SubRoute to add to its parent, but only
146         // load dynamic data so the duplicate static members don't get displayed
147         Node::json["@odata.id"] =
148             "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
149         entityPrivileges = {
150             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
151             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
152             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
153             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
154             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
155             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
156     }
157 
158   private:
159     /**
160      * Functions triggers appropriate requests on DBus
161      */
162     void doGet(crow::Response &res, const crow::Request &req,
163                const std::vector<std::string> &params) override
164     {
165         // Collections don't include the static data added by SubRoute because
166         // it has a duplicate entry for members
167         auto getLogEntriesCallback =
168             [&res](const boost::system::error_code ec,
169                    const std::vector<std::string> &resp) {
170                 if (ec)
171                 {
172                     if (ec.value() !=
173                         boost::system::errc::no_such_file_or_directory)
174                     {
175                         BMCWEB_LOG_DEBUG << "failed to get entries ec: "
176                                          << ec.message();
177                         res.result(
178                             boost::beast::http::status::internal_server_error);
179                         res.end();
180                         return;
181                     }
182                 }
183                 res.jsonValue["@odata.type"] =
184                     "#LogEntryCollection.LogEntryCollection";
185                 res.jsonValue["@odata.context"] =
186                     "/redfish/v1/"
187                     "$metadata#LogEntryCollection.LogEntryCollection";
188                 res.jsonValue["Name"] = "Open BMC CPU Log Entries";
189                 res.jsonValue["Description"] = "Collection of CPU Log Entries";
190                 nlohmann::json &logentryArray = res.jsonValue["Members"];
191                 logentryArray = nlohmann::json::array();
192                 for (const std::string &objpath : resp)
193                 {
194                     // Don't list the immediate log
195                     if (objpath.compare(cpuLogImmediatePath) == 0)
196                     {
197                         continue;
198                     }
199                     std::size_t lastPos = objpath.rfind("/");
200                     if (lastPos != std::string::npos)
201                     {
202                         logentryArray.push_back(
203                             {{"@odata.id", "/redfish/v1/Managers/bmc/"
204                                            "LogServices/CpuLog/Entries/" +
205                                                objpath.substr(lastPos + 1)}});
206                     }
207                 }
208                 res.jsonValue["Members@odata.count"] = logentryArray.size();
209                 res.end();
210             };
211         crow::connections::systemBus->async_method_call(
212             std::move(getLogEntriesCallback),
213             "xyz.openbmc_project.ObjectMapper",
214             "/xyz/openbmc_project/object_mapper",
215             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
216             std::array<const char *, 1>{cpuLogInterface});
217     }
218 };
219 
220 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
221 {
222     nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
223     if (metaIt != cpuLog.end())
224     {
225         nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
226         if (tsIt != metaIt->end())
227         {
228             const std::string *logTime = tsIt->get_ptr<const std::string *>();
229             if (logTime != nullptr)
230             {
231                 return *logTime;
232             }
233         }
234     }
235     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
236 
237     return std::string();
238 }
239 
240 class CpuLogEntry : public Node
241 {
242   public:
243     CpuLogEntry(CrowApp &app) :
244         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
245              std::string())
246     {
247         entityPrivileges = {
248             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
249             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
250             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
251             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
252             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
253             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
254     }
255 
256   private:
257     void doGet(crow::Response &res, const crow::Request &req,
258                const std::vector<std::string> &params) override
259     {
260         if (params.size() != 1)
261         {
262             res.result(boost::beast::http::status::internal_server_error);
263             res.end();
264             return;
265         }
266         const uint8_t logId = std::atoi(params[0].c_str());
267         auto getStoredLogCallback = [&res,
268                                      logId](const boost::system::error_code ec,
269                                             const sdbusplus::message::variant<
270                                                 std::string> &resp) {
271             if (ec)
272             {
273                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
274                 res.result(boost::beast::http::status::internal_server_error);
275                 res.end();
276                 return;
277             }
278             const std::string *log = mapbox::getPtr<const std::string>(resp);
279             if (log == nullptr)
280             {
281                 res.result(boost::beast::http::status::internal_server_error);
282                 res.end();
283                 return;
284             }
285             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
286             if (j.is_discarded())
287             {
288                 res.result(boost::beast::http::status::internal_server_error);
289                 res.end();
290                 return;
291             }
292             std::string t = getLogCreatedTime(j);
293             res.jsonValue = {
294                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
295                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
296                 {"@odata.id",
297                  "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
298                      std::to_string(logId)},
299                 {"Name", "CPU Debug Log"},
300                 {"Id", logId},
301                 {"EntryType", "Oem"},
302                 {"OemRecordFormat", "Intel CPU Log"},
303                 {"Oem", {{"Intel", std::move(j)}}},
304                 {"Created", std::move(t)}};
305             res.end();
306         };
307         crow::connections::systemBus->async_method_call(
308             std::move(getStoredLogCallback), cpuLogObject,
309             cpuLogPath + std::string("/") + std::to_string(logId),
310             "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
311     }
312 };
313 
314 class ImmediateCpuLog : public Node
315 {
316   public:
317     ImmediateCpuLog(CrowApp &app) :
318         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
319                   "CpuLog.Immediate")
320     {
321         entityPrivileges = {
322             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
323             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
324             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
325             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
326             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
327             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
328     }
329 
330   private:
331     void doPost(crow::Response &res, const crow::Request &req,
332                 const std::vector<std::string> &params) override
333     {
334         static std::unique_ptr<sdbusplus::bus::match::match>
335             immediateLogMatcher;
336 
337         // Only allow one Immediate Log request at a time
338         if (immediateLogMatcher != nullptr)
339         {
340             res.addHeader("Retry-After", "30");
341             res.result(boost::beast::http::status::service_unavailable);
342             messages::addMessageToJson(
343                 res.jsonValue, messages::serviceTemporarilyUnavailable("30"),
344                 "/CpuLog.Immediate");
345             res.end();
346             return;
347         }
348         // Make this static so it survives outside this method
349         static boost::asio::deadline_timer timeout(*req.ioService);
350 
351         timeout.expires_from_now(boost::posix_time::seconds(30));
352         timeout.async_wait([&res](const boost::system::error_code &ec) {
353             immediateLogMatcher = nullptr;
354             if (ec)
355             {
356                 // operation_aborted is expected if timer is canceled before
357                 // completion.
358                 if (ec != boost::asio::error::operation_aborted)
359                 {
360                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
361                 }
362                 return;
363             }
364             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
365 
366             res.result(boost::beast::http::status::internal_server_error);
367             res.end();
368         });
369 
370         auto immediateLogMatcherCallback = [&res](
371                                                sdbusplus::message::message &m) {
372             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
373             boost::system::error_code ec;
374             timeout.cancel(ec);
375             if (ec)
376             {
377                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
378             }
379             sdbusplus::message::object_path objPath;
380             boost::container::flat_map<
381                 std::string,
382                 boost::container::flat_map<
383                     std::string, sdbusplus::message::variant<std::string>>>
384                 interfacesAdded;
385             m.read(objPath, interfacesAdded);
386             const std::string *log = mapbox::getPtr<const std::string>(
387                 interfacesAdded[cpuLogInterface]["Log"]);
388             if (log == nullptr)
389             {
390                 res.result(boost::beast::http::status::internal_server_error);
391                 res.end();
392                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
393                 // match object inside which this lambda is executing.  Once it
394                 // is set to nullptr, the match object will be destroyed and the
395                 // lambda will lose its context, including res, so it needs to
396                 // be the last thing done.
397                 immediateLogMatcher = nullptr;
398                 return;
399             }
400             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
401             if (j.is_discarded())
402             {
403                 res.result(boost::beast::http::status::internal_server_error);
404                 res.end();
405                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
406                 // match object inside which this lambda is executing.  Once it
407                 // is set to nullptr, the match object will be destroyed and the
408                 // lambda will lose its context, including res, so it needs to
409                 // be the last thing done.
410                 immediateLogMatcher = nullptr;
411                 return;
412             }
413             std::string t = getLogCreatedTime(j);
414             res.jsonValue = {
415                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
416                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
417                 {"Name", "CPU Debug Log"},
418                 {"EntryType", "Oem"},
419                 {"OemRecordFormat", "Intel CPU Log"},
420                 {"Oem", {{"Intel", std::move(j)}}},
421                 {"Created", std::move(t)}};
422             res.end();
423             // Careful with immediateLogMatcher.  It is a unique_ptr to the
424             // match object inside which this lambda is executing.  Once it is
425             // set to nullptr, the match object will be destroyed and the lambda
426             // will lose its context, including res, so it needs to be the last
427             // thing done.
428             immediateLogMatcher = nullptr;
429         };
430         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
431             *crow::connections::systemBus,
432             sdbusplus::bus::match::rules::interfacesAdded() +
433                 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
434             std::move(immediateLogMatcherCallback));
435 
436         auto generateImmediateLogCallback =
437             [&res](const boost::system::error_code ec,
438                    const std::string &resp) {
439                 if (ec)
440                 {
441                     if (ec.value() ==
442                         boost::system::errc::operation_not_supported)
443                     {
444                         messages::addMessageToJson(
445                             res.jsonValue, messages::resourceInStandby(),
446                             "/CpuLog.Immediate");
447                         res.result(
448                             boost::beast::http::status::service_unavailable);
449                     }
450                     else
451                     {
452                         res.result(
453                             boost::beast::http::status::internal_server_error);
454                     }
455                     res.end();
456                     boost::system::error_code timeoutec;
457                     timeout.cancel(timeoutec);
458                     if (timeoutec)
459                     {
460                         BMCWEB_LOG_ERROR << "error canceling timer "
461                                          << timeoutec;
462                     }
463                     immediateLogMatcher = nullptr;
464                     return;
465                 }
466             };
467         crow::connections::systemBus->async_method_call(
468             std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
469             cpuLogImmediateInterface, "GenerateImmediateLog");
470     }
471 };
472 
473 class SendRawPeci : public Node
474 {
475   public:
476     SendRawPeci(CrowApp &app) :
477         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
478                   "CpuLog.SendRawPeci")
479     {
480         entityPrivileges = {
481             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
482             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
483             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
484             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
485             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
486             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
487     }
488 
489   private:
490     void doPost(crow::Response &res, const crow::Request &req,
491                 const std::vector<std::string> &params) override
492     {
493         // Get the Raw PECI command from the request
494         nlohmann::json rawPeciCmd;
495         if (!json_util::processJsonFromRequest(res, req, rawPeciCmd))
496         {
497             return;
498         }
499         // Get the Client Address from the request
500         nlohmann::json::const_iterator caIt = rawPeciCmd.find("ClientAddress");
501         if (caIt == rawPeciCmd.end())
502         {
503             messages::addMessageToJson(
504                 res.jsonValue, messages::propertyMissing("ClientAddress"),
505                 "/ClientAddress");
506             res.result(boost::beast::http::status::bad_request);
507             res.end();
508             return;
509         }
510         const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
511         if (ca == nullptr)
512         {
513             messages::addMessageToJson(
514                 res.jsonValue,
515                 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"),
516                 "/ClientAddress");
517             res.result(boost::beast::http::status::bad_request);
518             res.end();
519             return;
520         }
521         // Get the Read Length from the request
522         const uint8_t clientAddress = static_cast<uint8_t>(*ca);
523         nlohmann::json::const_iterator rlIt = rawPeciCmd.find("ReadLength");
524         if (rlIt == rawPeciCmd.end())
525         {
526             messages::addMessageToJson(res.jsonValue,
527                                        messages::propertyMissing("ReadLength"),
528                                        "/ReadLength");
529             res.result(boost::beast::http::status::bad_request);
530             res.end();
531             return;
532         }
533         const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
534         if (rl == nullptr)
535         {
536             messages::addMessageToJson(
537                 res.jsonValue,
538                 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"),
539                 "/ReadLength");
540             res.result(boost::beast::http::status::bad_request);
541             res.end();
542             return;
543         }
544         // Get the PECI Command from the request
545         const uint32_t readLength = static_cast<uint32_t>(*rl);
546         nlohmann::json::const_iterator pcIt = rawPeciCmd.find("PECICommand");
547         if (pcIt == rawPeciCmd.end())
548         {
549             messages::addMessageToJson(res.jsonValue,
550                                        messages::propertyMissing("PECICommand"),
551                                        "/PECICommand");
552             res.result(boost::beast::http::status::bad_request);
553             res.end();
554             return;
555         }
556         std::vector<uint8_t> peciCommand;
557         for (auto pc : *pcIt)
558         {
559             const uint64_t *val = pc.get_ptr<const uint64_t *>();
560             if (val == nullptr)
561             {
562                 messages::addMessageToJson(
563                     res.jsonValue,
564                     messages::propertyValueTypeError(
565                         pc.dump(),
566                         "PECICommand/" + std::to_string(peciCommand.size())),
567                     "/PECICommand");
568                 res.result(boost::beast::http::status::bad_request);
569                 res.end();
570                 return;
571             }
572             peciCommand.push_back(static_cast<uint8_t>(*val));
573         }
574         // Callback to return the Raw PECI response
575         auto sendRawPeciCallback = [&res](const boost::system::error_code ec,
576                                           const std::vector<uint8_t> &resp) {
577             if (ec)
578             {
579                 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
580                                  << ec.message();
581                 res.result(boost::beast::http::status::internal_server_error);
582                 res.end();
583                 return;
584             }
585             res.jsonValue = {{"Name", "PECI Command Response"},
586                              {"PECIResponse", resp}};
587             res.end();
588         };
589         // Call the SendRawPECI command with the provided data
590         crow::connections::systemBus->async_method_call(
591             std::move(sendRawPeciCallback), cpuLogObject, cpuLogPath,
592             cpuLogRawPeciInterface, "SendRawPeci", clientAddress, readLength,
593             peciCommand);
594     }
595 };
596 
597 } // namespace redfish
598