blob: b772bf1ddc5b0b31dcf7033a4f2374ea8a4e7f0f [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#pragma once
#include "registries.hpp"
#include "registries/base_message_registry.hpp"
#include "registries/openbmc_message_registry.hpp"
#include "task.hpp"
#include <systemd/sd-journal.h>
#include <unistd.h>
#include <app.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/beast/core/span.hpp>
#include <boost/beast/http.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/system/linux_error.hpp>
#include <error_messages.hpp>
#include <filesystem>
#include <optional>
#include <string_view>
#include <variant>
namespace redfish
{
constexpr char const* crashdumpObject = "com.intel.crashdump";
constexpr char const* crashdumpPath = "/com/intel/crashdump";
constexpr char const* crashdumpInterface = "com.intel.crashdump";
constexpr char const* deleteAllInterface =
"xyz.openbmc_project.Collection.DeleteAll";
constexpr char const* crashdumpOnDemandInterface =
"com.intel.crashdump.OnDemand";
constexpr char const* crashdumpTelemetryInterface =
"com.intel.crashdump.Telemetry";
namespace message_registries
{
static const Message* getMessageFromRegistry(
const std::string& messageKey,
const boost::beast::span<const MessageEntry> registry)
{
boost::beast::span<const MessageEntry>::const_iterator messageIt =
std::find_if(registry.cbegin(), registry.cend(),
[&messageKey](const MessageEntry& messageEntry) {
return !std::strcmp(messageEntry.first,
messageKey.c_str());
});
if (messageIt != registry.cend())
{
return &messageIt->second;
}
return nullptr;
}
static const Message* getMessage(const std::string_view& messageID)
{
// Redfish MessageIds are in the form
// RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
// the right Message
std::vector<std::string> fields;
fields.reserve(4);
boost::split(fields, messageID, boost::is_any_of("."));
std::string& registryName = fields[0];
std::string& messageKey = fields[3];
// Find the right registry and check it for the MessageKey
if (std::string(base::header.registryPrefix) == registryName)
{
return getMessageFromRegistry(
messageKey, boost::beast::span<const MessageEntry>(base::registry));
}
if (std::string(openbmc::header.registryPrefix) == registryName)
{
return getMessageFromRegistry(
messageKey,
boost::beast::span<const MessageEntry>(openbmc::registry));
}
return nullptr;
}
} // namespace message_registries
namespace fs = std::filesystem;
using GetManagedPropertyType = boost::container::flat_map<
std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t,
int32_t, uint32_t, int64_t, uint64_t, double>>;
using GetManagedObjectsType = boost::container::flat_map<
sdbusplus::message::object_path,
boost::container::flat_map<std::string, GetManagedPropertyType>>;
inline std::string translateSeverityDbusToRedfish(const std::string& s)
{
if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
{
return "Critical";
}
if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
{
return "OK";
}
if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
{
return "Warning";
}
return "";
}
inline static int getJournalMetadata(sd_journal* journal,
const std::string_view& field,
std::string_view& contents)
{
const char* data = nullptr;
size_t length = 0;
int ret = 0;
// Get the metadata from the requested field of the journal entry
ret = sd_journal_get_data(journal, field.data(),
reinterpret_cast<const void**>(&data), &length);
if (ret < 0)
{
return ret;
}
contents = std::string_view(data, length);
// Only use the content after the "=" character.
contents.remove_prefix(std::min(contents.find('=') + 1, contents.size()));
return ret;
}
inline static int getJournalMetadata(sd_journal* journal,
const std::string_view& field,
const int& base, long int& contents)
{
int ret = 0;
std::string_view metadata;
// Get the metadata from the requested field of the journal entry
ret = getJournalMetadata(journal, field, metadata);
if (ret < 0)
{
return ret;
}
contents = strtol(metadata.data(), nullptr, base);
return ret;
}
inline static bool getEntryTimestamp(sd_journal* journal,
std::string& entryTimestamp)
{
int ret = 0;
uint64_t timestamp = 0;
ret = sd_journal_get_realtime_usec(journal, &timestamp);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
<< strerror(-ret);
return false;
}
entryTimestamp = crow::utility::getDateTime(
static_cast<std::time_t>(timestamp / 1000 / 1000));
return true;
}
static bool getSkipParam(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req, uint64_t& skip)
{
boost::urls::url_view::params_type::iterator it =
req.urlParams.find("$skip");
if (it != req.urlParams.end())
{
std::string skipParam = it->value();
char* ptr = nullptr;
skip = std::strtoul(skipParam.c_str(), &ptr, 10);
if (skipParam.empty() || *ptr != '\0')
{
messages::queryParameterValueTypeError(
asyncResp->res, std::string(skipParam), "$skip");
return false;
}
}
return true;
}
static constexpr const uint64_t maxEntriesPerPage = 1000;
static bool getTopParam(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req, uint64_t& top)
{
boost::urls::url_view::params_type::iterator it =
req.urlParams.find("$top");
if (it != req.urlParams.end())
{
std::string topParam = it->value();
char* ptr = nullptr;
top = std::strtoul(topParam.c_str(), &ptr, 10);
if (topParam.empty() || *ptr != '\0')
{
messages::queryParameterValueTypeError(
asyncResp->res, std::string(topParam), "$top");
return false;
}
if (top < 1U || top > maxEntriesPerPage)
{
messages::queryParameterOutOfRange(
asyncResp->res, std::to_string(top), "$top",
"1-" + std::to_string(maxEntriesPerPage));
return false;
}
}
return true;
}
inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID,
const bool firstEntry = true)
{
int ret = 0;
static uint64_t prevTs = 0;
static int index = 0;
if (firstEntry)
{
prevTs = 0;
}
// Get the entry timestamp
uint64_t curTs = 0;
ret = sd_journal_get_realtime_usec(journal, &curTs);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
<< strerror(-ret);
return false;
}
// If the timestamp isn't unique, increment the index
if (curTs == prevTs)
{
index++;
}
else
{
// Otherwise, reset it
index = 0;
}
// Save the timestamp
prevTs = curTs;
entryID = std::to_string(curTs);
if (index > 0)
{
entryID += "_" + std::to_string(index);
}
return true;
}
static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
const bool firstEntry = true)
{
static time_t prevTs = 0;
static int index = 0;
if (firstEntry)
{
prevTs = 0;
}
// Get the entry timestamp
std::time_t curTs = 0;
std::tm timeStruct = {};
std::istringstream entryStream(logEntry);
if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
{
curTs = std::mktime(&timeStruct);
}
// If the timestamp isn't unique, increment the index
if (curTs == prevTs)
{
index++;
}
else
{
// Otherwise, reset it
index = 0;
}
// Save the timestamp
prevTs = curTs;
entryID = std::to_string(curTs);
if (index > 0)
{
entryID += "_" + std::to_string(index);
}
return true;
}
inline static bool
getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, uint64_t& timestamp,
uint64_t& index)
{
if (entryID.empty())
{
return false;
}
// Convert the unique ID back to a timestamp to find the entry
std::string_view tsStr(entryID);
auto underscorePos = tsStr.find('_');
if (underscorePos != tsStr.npos)
{
// Timestamp has an index
tsStr.remove_suffix(tsStr.size() - underscorePos);
std::string_view indexStr(entryID);
indexStr.remove_prefix(underscorePos + 1);
std::size_t pos;
try
{
index = std::stoul(std::string(indexStr), &pos);
}
catch (std::invalid_argument&)
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
catch (std::out_of_range&)
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
if (pos != indexStr.size())
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
}
// Timestamp has no index
std::size_t pos;
try
{
timestamp = std::stoull(std::string(tsStr), &pos);
}
catch (std::invalid_argument&)
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
catch (std::out_of_range&)
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
if (pos != tsStr.size())
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return false;
}
return true;
}
static bool
getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
{
static const std::filesystem::path redfishLogDir = "/var/log";
static const std::string redfishLogFilename = "redfish";
// Loop through the directory looking for redfish log files
for (const std::filesystem::directory_entry& dirEnt :
std::filesystem::directory_iterator(redfishLogDir))
{
// If we find a redfish log file, save the path
std::string filename = dirEnt.path().filename();
if (boost::starts_with(filename, redfishLogFilename))
{
redfishLogFiles.emplace_back(redfishLogDir / filename);
}
}
// As the log files rotate, they are appended with a ".#" that is higher for
// the older logs. Since we don't expect more than 10 log files, we
// can just sort the list to get them in order from newest to oldest
std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
return !redfishLogFiles.empty();
}
inline void
getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpType)
{
std::string dumpPath;
if (dumpType == "BMC")
{
dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
}
else if (dumpType == "System")
{
dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
}
else
{
BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType;
messages::internalError(asyncResp->res);
return;
}
crow::connections::systemBus->async_method_call(
[asyncResp, dumpPath, dumpType](const boost::system::error_code ec,
GetManagedObjectsType& resp) {
if (ec)
{
BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
entriesArray = nlohmann::json::array();
std::string dumpEntryPath =
"/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(dumpType)) +
"/entry/";
for (auto& object : resp)
{
if (object.first.str.find(dumpEntryPath) == std::string::npos)
{
continue;
}
std::time_t timestamp;
uint64_t size = 0;
entriesArray.push_back({});
nlohmann::json& thisEntry = entriesArray.back();
std::string entryID = object.first.filename();
if (entryID.empty())
{
continue;
}
for (auto& interfaceMap : object.second)
{
if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Size")
{
auto sizePtr =
std::get_if<uint64_t>(&propertyMap.second);
if (sizePtr == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
size = *sizePtr;
break;
}
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Time.EpochTime")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Elapsed")
{
const uint64_t* usecsTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (usecsTimeStamp == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
timestamp =
static_cast<std::time_t>(*usecsTimeStamp);
break;
}
}
}
}
thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry";
thisEntry["@odata.id"] = dumpPath + entryID;
thisEntry["Id"] = entryID;
thisEntry["EntryType"] = "Event";
thisEntry["Created"] = crow::utility::getDateTime(timestamp);
thisEntry["Name"] = dumpType + " Dump Entry";
thisEntry["AdditionalDataSizeBytes"] = size;
if (dumpType == "BMC")
{
thisEntry["DiagnosticDataType"] = "Manager";
thisEntry["AdditionalDataURI"] =
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/" +
entryID + "/attachment";
}
else if (dumpType == "System")
{
thisEntry["DiagnosticDataType"] = "OEM";
thisEntry["OEMDiagnosticDataType"] = "System";
thisEntry["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/Dump/Entries/" +
entryID + "/attachment";
}
}
asyncResp->res.jsonValue["Members@odata.count"] =
entriesArray.size();
},
"xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
}
inline void
getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, const std::string& dumpType)
{
std::string dumpPath;
if (dumpType == "BMC")
{
dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
}
else if (dumpType == "System")
{
dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
}
else
{
BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType;
messages::internalError(asyncResp->res);
return;
}
crow::connections::systemBus->async_method_call(
[asyncResp, entryID, dumpPath, dumpType](
const boost::system::error_code ec, GetManagedObjectsType& resp) {
if (ec)
{
BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
bool foundDumpEntry = false;
std::string dumpEntryPath =
"/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(dumpType)) +
"/entry/";
for (auto& objectPath : resp)
{
if (objectPath.first.str != dumpEntryPath + entryID)
{
continue;
}
foundDumpEntry = true;
std::time_t timestamp;
uint64_t size = 0;
for (auto& interfaceMap : objectPath.second)
{
if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Size")
{
auto sizePtr =
std::get_if<uint64_t>(&propertyMap.second);
if (sizePtr == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
size = *sizePtr;
break;
}
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Time.EpochTime")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Elapsed")
{
const uint64_t* usecsTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (usecsTimeStamp == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
timestamp =
static_cast<std::time_t>(*usecsTimeStamp);
break;
}
}
}
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntry.v1_8_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] = dumpPath + entryID;
asyncResp->res.jsonValue["Id"] = entryID;
asyncResp->res.jsonValue["EntryType"] = "Event";
asyncResp->res.jsonValue["Created"] =
crow::utility::getDateTime(timestamp);
asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry";
asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
if (dumpType == "BMC")
{
asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
asyncResp->res.jsonValue["AdditionalDataURI"] =
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/" +
entryID + "/attachment";
}
else if (dumpType == "System")
{
asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
asyncResp->res.jsonValue["OEMDiagnosticDataType"] =
"System";
asyncResp->res.jsonValue["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/Dump/Entries/" +
entryID + "/attachment";
}
}
if (foundDumpEntry == false)
{
BMCWEB_LOG_ERROR << "Can't find Dump Entry";
messages::internalError(asyncResp->res);
return;
}
},
"xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
}
inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID,
const std::string& dumpType)
{
auto respHandler = [asyncResp,
entryID](const boost::system::error_code ec) {
BMCWEB_LOG_DEBUG << "Dump Entry doDelete callback: Done";
if (ec)
{
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return;
}
BMCWEB_LOG_ERROR << "Dump (DBus) doDelete respHandler got error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
};
crow::connections::systemBus->async_method_call(
respHandler, "xyz.openbmc_project.Dump.Manager",
"/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/" +
entryID,
"xyz.openbmc_project.Object.Delete", "Delete");
}
inline void
createDumpTaskCallback(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const uint32_t& dumpId, const std::string& dumpPath,
const std::string& dumpType)
{
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[dumpId, dumpPath, dumpType](
boost::system::error_code err, sdbusplus::message::message& m,
const std::shared_ptr<task::TaskData>& taskData) {
if (err)
{
BMCWEB_LOG_ERROR << "Error in creating a dump";
taskData->state = "Cancelled";
return task::completed;
}
std::vector<std::pair<
std::string,
std::vector<std::pair<std::string, std::variant<std::string>>>>>
interfacesList;
sdbusplus::message::object_path objPath;
m.read(objPath, interfacesList);
if (objPath.str ==
"/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(dumpType)) +
"/entry/" + std::to_string(dumpId))
{
nlohmann::json retMessage = messages::success();
taskData->messages.emplace_back(retMessage);
std::string headerLoc =
"Location: " + dumpPath + std::to_string(dumpId);
taskData->payload->httpHeaders.emplace_back(
std::move(headerLoc));
taskData->state = "Completed";
return task::completed;
}
return task::completed;
},
"type='signal',interface='org.freedesktop.DBus."
"ObjectManager',"
"member='InterfacesAdded', "
"path='/xyz/openbmc_project/dump'");
task->startTimer(std::chrono::minutes(3));
task->populateResp(asyncResp->res);
task->payload.emplace(req);
}
inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req, const std::string& dumpType)
{
std::string dumpPath;
if (dumpType == "BMC")
{
dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
}
else if (dumpType == "System")
{
dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
}
else
{
BMCWEB_LOG_ERROR << "Invalid dump type: " << dumpType;
messages::internalError(asyncResp->res);
return;
}
std::optional<std::string> diagnosticDataType;
std::optional<std::string> oemDiagnosticDataType;
if (!redfish::json_util::readJson(
req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
"OEMDiagnosticDataType", oemDiagnosticDataType))
{
return;
}
if (dumpType == "System")
{
if (!oemDiagnosticDataType || !diagnosticDataType)
{
BMCWEB_LOG_ERROR << "CreateDump action parameter "
"'DiagnosticDataType'/"
"'OEMDiagnosticDataType' value not found!";
messages::actionParameterMissing(
asyncResp->res, "CollectDiagnosticData",
"DiagnosticDataType & OEMDiagnosticDataType");
return;
}
if ((*oemDiagnosticDataType != "System") ||
(*diagnosticDataType != "OEM"))
{
BMCWEB_LOG_ERROR << "Wrong parameter values passed";
messages::invalidObject(asyncResp->res,
"System Dump creation parameters");
return;
}
}
else if (dumpType == "BMC")
{
if (!diagnosticDataType)
{
BMCWEB_LOG_ERROR << "CreateDump action parameter "
"'DiagnosticDataType' not found!";
messages::actionParameterMissing(
asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
return;
}
if (*diagnosticDataType != "Manager")
{
BMCWEB_LOG_ERROR
<< "Wrong parameter value passed for 'DiagnosticDataType'";
messages::invalidObject(asyncResp->res,
"BMC Dump creation parameters");
return;
}
}
crow::connections::systemBus->async_method_call(
[asyncResp, req, dumpPath, dumpType](const boost::system::error_code ec,
const uint32_t& dumpId) {
if (ec)
{
BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "Dump Created. Id: " << dumpId;
createDumpTaskCallback(req, asyncResp, dumpId, dumpPath, dumpType);
},
"xyz.openbmc_project.Dump.Manager",
"/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(dumpType)),
"xyz.openbmc_project.Dump.Create", "CreateDump");
}
inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpType)
{
std::string dumpTypeLowerCopy =
std::string(boost::algorithm::to_lower_copy(dumpType));
crow::connections::systemBus->async_method_call(
[asyncResp, dumpType](const boost::system::error_code ec,
const std::vector<std::string>& subTreePaths) {
if (ec)
{
BMCWEB_LOG_ERROR << "resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
for (const std::string& path : subTreePaths)
{
sdbusplus::message::object_path objPath(path);
std::string logID = objPath.filename();
if (logID.empty())
{
continue;
}
deleteDumpEntry(asyncResp, logID, dumpType);
}
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
"/xyz/openbmc_project/dump/" + dumpTypeLowerCopy, 0,
std::array<std::string, 1>{"xyz.openbmc_project.Dump.Entry." +
dumpType});
}
inline static void parseCrashdumpParameters(
const std::vector<std::pair<std::string, VariantType>>& params,
std::string& filename, std::string& timestamp, std::string& logfile)
{
for (auto property : params)
{
if (property.first == "Timestamp")
{
const std::string* value =
std::get_if<std::string>(&property.second);
if (value != nullptr)
{
timestamp = *value;
}
}
else if (property.first == "Filename")
{
const std::string* value =
std::get_if<std::string>(&property.second);
if (value != nullptr)
{
filename = *value;
}
}
else if (property.first == "Log")
{
const std::string* value =
std::get_if<std::string>(&property.second);
if (value != nullptr)
{
logfile = *value;
}
}
}
}
constexpr char const* postCodeIface = "xyz.openbmc_project.State.Boot.PostCode";
inline void requestRoutesSystemLogServiceCollection(App& app)
{
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogServiceCollection.LogServiceCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices";
asyncResp->res.jsonValue["Name"] =
"System Log Services Collection";
asyncResp->res.jsonValue["Description"] =
"Collection of LogServices for this Computer System";
nlohmann::json& logServiceArray =
asyncResp->res.jsonValue["Members"];
logServiceArray = nlohmann::json::array();
logServiceArray.push_back(
{{"@odata.id",
"/redfish/v1/Systems/system/LogServices/EventLog"}});
#ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
logServiceArray.push_back(
{{"@odata.id",
"/redfish/v1/Systems/system/LogServices/Dump"}});
#endif
#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
logServiceArray.push_back(
{{"@odata.id",
"/redfish/v1/Systems/system/LogServices/Crashdump"}});
#endif
asyncResp->res.jsonValue["Members@odata.count"] =
logServiceArray.size();
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code ec,
const std::vector<std::string>& subtreePath) {
if (ec)
{
BMCWEB_LOG_ERROR << ec;
return;
}
for (auto& pathStr : subtreePath)
{
if (pathStr.find("PostCode") != std::string::npos)
{
nlohmann::json& logServiceArrayLocal =
asyncResp->res.jsonValue["Members"];
logServiceArrayLocal.push_back(
{{"@odata.id", "/redfish/v1/Systems/system/"
"LogServices/PostCodes"}});
asyncResp->res
.jsonValue["Members@odata.count"] =
logServiceArrayLocal.size();
return;
}
}
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
0, std::array<const char*, 1>{postCodeIface});
});
}
inline void requestRoutesEventLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
.privileges({{"Login"}})
.methods(
boost::beast::http::verb::
get)([](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/EventLog";
asyncResp->res.jsonValue["@odata.type"] =
"#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["Name"] = "Event Log Service";
asyncResp->res.jsonValue["Description"] =
"System Event Log Service";
asyncResp->res.jsonValue["Id"] = "EventLog";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"] = {
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
{"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
"Actions/LogService.ClearLog"}};
});
}
inline void requestRoutesJournalEventLogClear(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
// Clear the EventLog by deleting the log files
std::vector<std::filesystem::path> redfishLogFiles;
if (getRedfishLogFiles(redfishLogFiles))
{
for (const std::filesystem::path& file : redfishLogFiles)
{
std::error_code ec;
std::filesystem::remove(file, ec);
}
}
// Reload rsyslog so it knows to start new log files
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "Failed to reload rsyslog: "
<< ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
},
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "ReloadUnit",
"rsyslog.service", "replace");
});
}
static int fillEventLogEntryJson(const std::string& logEntryID,
const std::string& logEntry,
nlohmann::json& logEntryJson)
{
// The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
// First get the Timestamp
size_t space = logEntry.find_first_of(' ');
if (space == std::string::npos)
{
return 1;
}
std::string timestamp = logEntry.substr(0, space);
// Then get the log contents
size_t entryStart = logEntry.find_first_not_of(' ', space);
if (entryStart == std::string::npos)
{
return 1;
}
std::string_view entry(logEntry);
entry.remove_prefix(entryStart);
// Use split to separate the entry into its fields
std::vector<std::string> logEntryFields;
boost::split(logEntryFields, entry, boost::is_any_of(","),
boost::token_compress_on);
// We need at least a MessageId to be valid
if (logEntryFields.size() < 1)
{
return 1;
}
std::string& messageID = logEntryFields[0];
// Get the Message from the MessageRegistry
const message_registries::Message* message =
message_registries::getMessage(messageID);
std::string msg;
std::string severity;
if (message != nullptr)
{
msg = message->message;
severity = message->severity;
}
// Get the MessageArgs from the log if there are any
boost::beast::span<std::string> messageArgs;
if (logEntryFields.size() > 1)
{
std::string& messageArgsStart = logEntryFields[1];
// If the first string is empty, assume there are no MessageArgs
std::size_t messageArgsSize = 0;
if (!messageArgsStart.empty())
{
messageArgsSize = logEntryFields.size() - 1;
}
messageArgs = {&messageArgsStart, messageArgsSize};
// Fill the MessageArgs into the Message
int i = 0;
for (const std::string& messageArg : messageArgs)
{
std::string argStr = "%" + std::to_string(++i);
size_t argPos = msg.find(argStr);
if (argPos != std::string::npos)
{
msg.replace(argPos, argStr.length(), messageArg);
}
}
}
// Get the Created time from the timestamp. The log timestamp is in RFC3339
// format which matches the Redfish format except for the fractional seconds
// between the '.' and the '+', so just remove them.
std::size_t dot = timestamp.find_first_of('.');
std::size_t plus = timestamp.find_first_of('+');
if (dot != std::string::npos && plus != std::string::npos)
{
timestamp.erase(dot, plus - dot);
}
// Fill in the log entry with the gathered data
logEntryJson = {
{"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
logEntryID},
{"Name", "System Event Log Entry"},
{"Id", logEntryID},
{"Message", std::move(msg)},
{"MessageId", std::move(messageID)},
{"MessageArgs", messageArgs},
{"EntryType", "Event"},
{"Severity", std::move(severity)},
{"Created", std::move(timestamp)}};
return 0;
}
inline void requestRoutesJournalEventLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
uint64_t skip = 0;
uint64_t top = maxEntriesPerPage; // Show max entries by default
if (!getSkipParam(asyncResp, req, skip))
{
return;
}
if (!getTopParam(asyncResp, req, top))
{
return;
}
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/EventLog/Entries";
asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of System Event Log Entries";
nlohmann::json& logEntryArray =
asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
// Go through the log files and create a unique ID for each
// entry
std::vector<std::filesystem::path> redfishLogFiles;
getRedfishLogFiles(redfishLogFiles);
uint64_t entryCount = 0;
std::string logEntry;
// Oldest logs are in the last file, so start there and loop
// backwards
for (auto it = redfishLogFiles.rbegin();
it < redfishLogFiles.rend(); it++)
{
std::ifstream logStream(*it);
if (!logStream.is_open())
{
continue;
}
// Reset the unique ID on the first entry
bool firstEntry = true;
while (std::getline(logStream, logEntry))
{
entryCount++;
// Handle paging using skip (number of entries to skip
// from the start) and top (number of entries to
// display)
if (entryCount <= skip || entryCount > skip + top)
{
continue;
}
std::string idStr;
if (!getUniqueEntryID(logEntry, idStr, firstEntry))
{
continue;
}
if (firstEntry)
{
firstEntry = false;
}
logEntryArray.push_back({});
nlohmann::json& bmcLogEntry = logEntryArray.back();
if (fillEventLogEntryJson(idStr, logEntry,
bmcLogEntry) != 0)
{
messages::internalError(asyncResp->res);
return;
}
}
}
asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
if (skip + top < entryCount)
{
asyncResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Systems/system/LogServices/EventLog/"
"Entries?$skip=" +
std::to_string(skip + top);
}
});
}
inline void requestRoutesJournalEventLogEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
const std::string& targetID = param;
// Go through the log files and check the unique ID for each
// entry to find the target entry
std::vector<std::filesystem::path> redfishLogFiles;
getRedfishLogFiles(redfishLogFiles);
std::string logEntry;
// Oldest logs are in the last file, so start there and loop
// backwards
for (auto it = redfishLogFiles.rbegin();
it < redfishLogFiles.rend(); it++)
{
std::ifstream logStream(*it);
if (!logStream.is_open())
{
continue;
}
// Reset the unique ID on the first entry
bool firstEntry = true;
while (std::getline(logStream, logEntry))
{
std::string idStr;
if (!getUniqueEntryID(logEntry, idStr, firstEntry))
{
continue;
}
if (firstEntry)
{
firstEntry = false;
}
if (idStr == targetID)
{
if (fillEventLogEntryJson(
idStr, logEntry,
asyncResp->res.jsonValue) != 0)
{
messages::internalError(asyncResp->res);
return;
}
return;
}
}
}
// Requested ID was not found
messages::resourceMissingAtURI(asyncResp->res, targetID);
});
}
inline void requestRoutesDBusEventLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
.privileges({{"Login"}})
.methods(
boost::beast::http::verb::
get)([](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/EventLog/Entries";
asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of System Event Log Entries";
// DBus implementation of EventLog/Entries
// Make call to Logging Service to find all log entry objects
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code ec,
GetManagedObjectsType& resp) {
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR
<< "getLogEntriesIfaceData resp_handler got error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& entriesArray =
asyncResp->res.jsonValue["Members"];
entriesArray = nlohmann::json::array();
for (auto& objectPath : resp)
{
uint32_t* id = nullptr;
std::time_t timestamp{};
std::time_t updateTimestamp{};
std::string* severity = nullptr;
std::string* message = nullptr;
std::string* filePath = nullptr;
bool resolved = false;
for (auto& interfaceMap : objectPath.second)
{
if (interfaceMap.first ==
"xyz.openbmc_project.Logging.Entry")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Id")
{
id = std::get_if<uint32_t>(
&propertyMap.second);
}
else if (propertyMap.first == "Timestamp")
{
const uint64_t* millisTimeStamp =
std::get_if<uint64_t>(
&propertyMap.second);
if (millisTimeStamp != nullptr)
{
timestamp =
crow::utility::getTimestamp(
*millisTimeStamp);
}
}
else if (propertyMap.first ==
"UpdateTimestamp")
{
const uint64_t* millisTimeStamp =
std::get_if<uint64_t>(
&propertyMap.second);
if (millisTimeStamp != nullptr)
{
updateTimestamp =
crow::utility::getTimestamp(
*millisTimeStamp);
}
}
else if (propertyMap.first == "Severity")
{
severity = std::get_if<std::string>(
&propertyMap.second);
}
else if (propertyMap.first == "Message")
{
message = std::get_if<std::string>(
&propertyMap.second);
}
else if (propertyMap.first == "Resolved")
{
bool* resolveptr = std::get_if<bool>(
&propertyMap.second);
if (resolveptr == nullptr)
{
messages::internalError(
asyncResp->res);
return;
}
resolved = *resolveptr;
}
}
if (id == nullptr || message == nullptr ||
severity == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Common.FilePath")
{
for (auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Path")
{
filePath = std::get_if<std::string>(
&propertyMap.second);
}
}
}
}
// Object path without the
// xyz.openbmc_project.Logging.Entry interface, ignore
// and continue.
if (id == nullptr || message == nullptr ||
severity == nullptr)
{
continue;
}
entriesArray.push_back({});
nlohmann::json& thisEntry = entriesArray.back();
thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry";
thisEntry["@odata.id"] =
"/redfish/v1/Systems/system/"
"LogServices/EventLog/Entries/" +
std::to_string(*id);
thisEntry["Name"] = "System Event Log Entry";
thisEntry["Id"] = std::to_string(*id);
thisEntry["Message"] = *message;
thisEntry["Resolved"] = resolved;
thisEntry["EntryType"] = "Event";
thisEntry["Severity"] =
translateSeverityDbusToRedfish(*severity);
thisEntry["Created"] =
crow::utility::getDateTime(timestamp);
thisEntry["Modified"] =
crow::utility::getDateTime(updateTimestamp);
if (filePath != nullptr)
{
thisEntry["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/"
"EventLog/"
"Entries/" +
std::to_string(*id) + "/attachment";
}
}
std::sort(entriesArray.begin(), entriesArray.end(),
[](const nlohmann::json& left,
const nlohmann::json& right) {
return (left["Id"] <= right["Id"]);
});
asyncResp->res.jsonValue["Members@odata.count"] =
entriesArray.size();
},
"xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
});
}
inline void requestRoutesDBusEventLogEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param)
{
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
// DBus implementation of EventLog/Entries
// Make call to Logging Service to find all log entry objects
crow::connections::systemBus->async_method_call(
[asyncResp, entryID](const boost::system::error_code ec,
GetManagedPropertyType& resp) {
if (ec.value() == EBADR)
{
messages::resourceNotFound(
asyncResp->res, "EventLogEntry", entryID);
return;
}
if (ec)
{
BMCWEB_LOG_ERROR << "EventLogEntry (DBus) "
"resp_handler got error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
uint32_t* id = nullptr;
std::time_t timestamp{};
std::time_t updateTimestamp{};
std::string* severity = nullptr;
std::string* message = nullptr;
std::string* filePath = nullptr;
bool resolved = false;
for (auto& propertyMap : resp)
{
if (propertyMap.first == "Id")
{
id = std::get_if<uint32_t>(&propertyMap.second);
}
else if (propertyMap.first == "Timestamp")
{
const uint64_t* millisTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (millisTimeStamp != nullptr)
{
timestamp = crow::utility::getTimestamp(
*millisTimeStamp);
}
}
else if (propertyMap.first == "UpdateTimestamp")
{
const uint64_t* millisTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (millisTimeStamp != nullptr)
{
updateTimestamp =
crow::utility::getTimestamp(
*millisTimeStamp);
}
}
else if (propertyMap.first == "Severity")
{
severity = std::get_if<std::string>(
&propertyMap.second);
}
else if (propertyMap.first == "Message")
{
message = std::get_if<std::string>(
&propertyMap.second);
}
else if (propertyMap.first == "Resolved")
{
bool* resolveptr =
std::get_if<bool>(&propertyMap.second);
if (resolveptr == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
resolved = *resolveptr;
}
else if (propertyMap.first == "Path")
{
filePath = std::get_if<std::string>(
&propertyMap.second);
}
}
if (id == nullptr || message == nullptr ||
severity == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntry.v1_8_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/EventLog/"
"Entries/" +
std::to_string(*id);
asyncResp->res.jsonValue["Name"] =
"System Event Log Entry";
asyncResp->res.jsonValue["Id"] = std::to_string(*id);
asyncResp->res.jsonValue["Message"] = *message;
asyncResp->res.jsonValue["Resolved"] = resolved;
asyncResp->res.jsonValue["EntryType"] = "Event";
asyncResp->res.jsonValue["Severity"] =
translateSeverityDbusToRedfish(*severity);
asyncResp->res.jsonValue["Created"] =
crow::utility::getDateTime(timestamp);
asyncResp->res.jsonValue["Modified"] =
crow::utility::getDateTime(updateTimestamp);
if (filePath != nullptr)
{
asyncResp->res.jsonValue["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/"
"EventLog/"
"attachment/" +
std::to_string(*id);
}
},
"xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID,
"org.freedesktop.DBus.Properties", "GetAll", "");
});
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::patch)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryId) {
std::optional<bool> resolved;
if (!json_util::readJson(req, asyncResp->res, "Resolved",
resolved))
{
return;
}
BMCWEB_LOG_DEBUG << "Set Resolved";
crow::connections::systemBus->async_method_call(
[asyncResp, resolved,
entryId](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
},
"xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryId,
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Logging.Entry", "Resolved",
std::variant<bool>(*resolved));
});
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::delete_)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param)
{
BMCWEB_LOG_DEBUG << "Do delete single event entries.";
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
// Process response from Logging service.
auto respHandler = [asyncResp, entryID](
const boost::system::error_code ec) {
BMCWEB_LOG_DEBUG
<< "EventLogEntry (DBus) doDelete callback: Done";
if (ec)
{
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res,
"LogEntry", entryID);
return;
}
// TODO Handle for specific error code
BMCWEB_LOG_ERROR << "EventLogEntry (DBus) doDelete "
"respHandler got error "
<< ec;
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
asyncResp->res.result(boost::beast::http::status::ok);
};
// Make call to Logging service to request Delete Log
crow::connections::systemBus->async_method_call(
respHandler, "xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID,
"xyz.openbmc_project.Object.Delete", "Delete");
});
}
inline void requestRoutesDBusEventLogEntryDownload(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/"
"<str>/attachment")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param)
{
std::string_view acceptHeader = req.getHeaderValue("Accept");
// The iterators in boost/http/rfc7230.hpp end the string if '/'
// is found, so replace it with arbitrary character '|' which is
// not part of the Accept header syntax.
std::string acceptStr = boost::replace_all_copy(
std::string(acceptHeader), "/", "|");
boost::beast::http::ext_list acceptTypes{acceptStr};
bool supported = false;
for (const auto& type : acceptTypes)
{
if ((type.first == "*|*") ||
(type.first == "application|octet-stream"))
{
supported = true;
break;
}
}
if (!supported)
{
asyncResp->res.result(
boost::beast::http::status::bad_request);
return;
}
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
crow::connections::systemBus->async_method_call(
[asyncResp,
entryID](const boost::system::error_code ec,
const sdbusplus::message::unix_fd& unixfd) {
if (ec.value() == EBADR)
{
messages::resourceNotFound(
asyncResp->res, "EventLogAttachment", entryID);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
int fd = -1;
fd = dup(unixfd);
if (fd == -1)
{
messages::internalError(asyncResp->res);
return;
}
long long int size = lseek(fd, 0, SEEK_END);
if (size == -1)
{
messages::internalError(asyncResp->res);
return;
}
// Arbitrary max size of 64kb
constexpr int maxFileSize = 65536;
if (size > maxFileSize)
{
BMCWEB_LOG_ERROR
<< "File size exceeds maximum allowed size of "
<< maxFileSize;
messages::internalError(asyncResp->res);
return;
}
std::vector<char> data(static_cast<size_t>(size));
long long int rc = lseek(fd, 0, SEEK_SET);
if (rc == -1)
{
messages::internalError(asyncResp->res);
return;
}
rc = read(fd, data.data(), data.size());
if ((rc == -1) || (rc != size))
{
messages::internalError(asyncResp->res);
return;
}
close(fd);
std::string_view strData(data.data(), data.size());
std::string output =
crow::utility::base64encode(strData);
asyncResp->res.addHeader("Content-Type",
"application/octet-stream");
asyncResp->res.addHeader("Content-Transfer-Encoding",
"Base64");
asyncResp->res.body() = std::move(output);
},
"xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID,
"xyz.openbmc_project.Logging.Entry", "GetEntry");
});
}
inline void requestRoutesBMCLogServiceCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogServiceCollection.LogServiceCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices";
asyncResp->res.jsonValue["Name"] =
"Open BMC Log Services Collection";
asyncResp->res.jsonValue["Description"] =
"Collection of LogServices for this Manager";
nlohmann::json& logServiceArray =
asyncResp->res.jsonValue["Members"];
logServiceArray = nlohmann::json::array();
#ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
logServiceArray.push_back(
{{"@odata.id",
"/redfish/v1/Managers/bmc/LogServices/Dump"}});
#endif
#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
logServiceArray.push_back(
{{"@odata.id",
"/redfish/v1/Managers/bmc/LogServices/Journal"}});
#endif
asyncResp->res.jsonValue["Members@odata.count"] =
logServiceArray.size();
});
}
inline void requestRoutesBMCJournalLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
asyncResp->res.jsonValue["@odata.type"] =
"#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Journal";
asyncResp->res.jsonValue["Name"] =
"Open BMC Journal Log Service";
asyncResp->res.jsonValue["Description"] =
"BMC Journal Log Service";
asyncResp->res.jsonValue["Id"] = "BMC Journal";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] =
redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"] = {
{"@odata.id",
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
});
}
static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
sd_journal* journal,
nlohmann::json& bmcJournalLogEntryJson)
{
// Get the Log Entry contents
int ret = 0;
std::string message;
std::string_view syslogID;
ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: "
<< strerror(-ret);
}
if (!syslogID.empty())
{
message += std::string(syslogID) + ": ";
}
std::string_view msg;
ret = getJournalMetadata(journal, "MESSAGE", msg);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
return 1;
}
message += std::string(msg);
// Get the severity from the PRIORITY field
long int severity = 8; // Default to an invalid priority
ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
}
// Get the Created time from the timestamp
std::string entryTimeStr;
if (!getEntryTimestamp(journal, entryTimeStr))
{
return 1;
}
// Fill in the log entry with the gathered data
bmcJournalLogEntryJson = {
{"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
bmcJournalLogEntryID},
{"Name", "BMC Journal Entry"},
{"Id", bmcJournalLogEntryID},
{"Message", std::move(message)},
{"EntryType", "Oem"},
{"Severity", severity <= 2 ? "Critical"
: severity <= 4 ? "Warning"
: "OK"},
{"OemRecordFormat", "BMC Journal Entry"},
{"Created", std::move(entryTimeStr)}};
return 0;
}
inline void requestRoutesBMCJournalLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
static constexpr const long maxEntriesPerPage = 1000;
uint64_t skip = 0;
uint64_t top = maxEntriesPerPage; // Show max entries by default
if (!getSkipParam(asyncResp, req, skip))
{
return;
}
if (!getTopParam(asyncResp, req, top))
{
return;
}
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of BMC Journal Entries";
nlohmann::json& logEntryArray =
asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
// Go through the journal and use the timestamp to create a
// unique ID for each entry
sd_journal* journalTmp = nullptr;
int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to open journal: "
<< strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
std::unique_ptr<sd_journal, decltype(&sd_journal_close)>
journal(journalTmp, sd_journal_close);
journalTmp = nullptr;
uint64_t entryCount = 0;
// Reset the unique ID on the first entry
bool firstEntry = true;
SD_JOURNAL_FOREACH(journal.get())
{
entryCount++;
// Handle paging using skip (number of entries to skip from
// the start) and top (number of entries to display)
if (entryCount <= skip || entryCount > skip + top)
{
continue;
}
std::string idStr;
if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
{
continue;
}
if (firstEntry)
{
firstEntry = false;
}
logEntryArray.push_back({});
nlohmann::json& bmcJournalLogEntry = logEntryArray.back();
if (fillBMCJournalLogEntryJson(idStr, journal.get(),
bmcJournalLogEntry) != 0)
{
messages::internalError(asyncResp->res);
return;
}
}
asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
if (skip + top < entryCount)
{
asyncResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Managers/bmc/LogServices/Journal/"
"Entries?$skip=" +
std::to_string(skip + top);
}
});
}
inline void requestRoutesBMCJournalLogEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID) {
// Convert the unique ID back to a timestamp to find the entry
uint64_t ts = 0;
uint64_t index = 0;
if (!getTimestampFromID(asyncResp, entryID, ts, index))
{
return;
}
sd_journal* journalTmp = nullptr;
int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to open journal: "
<< strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
std::unique_ptr<sd_journal, decltype(&sd_journal_close)>
journal(journalTmp, sd_journal_close);
journalTmp = nullptr;
// Go to the timestamp in the log and move to the entry at the
// index tracking the unique ID
std::string idStr;
bool firstEntry = true;
ret = sd_journal_seek_realtime_usec(journal.get(), ts);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
<< strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
for (uint64_t i = 0; i <= index; i++)
{
sd_journal_next(journal.get());
if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
{
messages::internalError(asyncResp->res);
return;
}
if (firstEntry)
{
firstEntry = false;
}
}
// Confirm that the entry ID matches what was requested
if (idStr != entryID)
{
messages::resourceMissingAtURI(asyncResp->res, entryID);
return;
}
if (fillBMCJournalLogEntryJson(entryID, journal.get(),
asyncResp->res.jsonValue) != 0)
{
messages::internalError(asyncResp->res);
return;
}
});
}
inline void requestRoutesBMCDumpService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Dump";
asyncResp->res.jsonValue["@odata.type"] =
"#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Dump LogService";
asyncResp->res.jsonValue["Description"] = "BMC Dump LogService";
asyncResp->res.jsonValue["Id"] = "Dump";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] =
redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"] = {
{"@odata.id",
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}};
asyncResp->res.jsonValue["Actions"] = {
{"#LogService.ClearLog",
{{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/"
"Actions/LogService.ClearLog"}}},
{"#LogService.CollectDiagnosticData",
{{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/"
"Actions/LogService.CollectDiagnosticData"}}}};
});
}
inline void requestRoutesBMCDumpEntryCollection(App& app)
{
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries";
asyncResp->res.jsonValue["Name"] = "BMC Dump Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of BMC Dump Entries";
getDumpEntryCollection(asyncResp, "BMC");
});
}
inline void requestRoutesBMCDumpEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
getDumpEntryById(asyncResp, param, "BMC");
});
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::delete_)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
deleteDumpEntry(asyncResp, param, "bmc");
});
}
inline void requestRoutesBMCDumpCreate(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/"
"Actions/"
"LogService.CollectDiagnosticData/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
createDump(asyncResp, req, "BMC");
});
}
inline void requestRoutesBMCDumpClear(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/"
"Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
clearDump(asyncResp, "BMC");
});
}
inline void requestRoutesSystemDumpService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Dump";
asyncResp->res.jsonValue["@odata.type"] =
"#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Dump LogService";
asyncResp->res.jsonValue["Description"] =
"System Dump LogService";
asyncResp->res.jsonValue["Id"] = "Dump";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] =
redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"] = {
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/Dump/Entries"}};
asyncResp->res.jsonValue["Actions"] = {
{"#LogService.ClearLog",
{{"target",
"/redfish/v1/Systems/system/LogServices/Dump/Actions/"
"LogService.ClearLog"}}},
{"#LogService.CollectDiagnosticData",
{{"target",
"/redfish/v1/Systems/system/LogServices/Dump/Actions/"
"LogService.CollectDiagnosticData"}}}};
});
}
inline void requestRoutesSystemDumpEntryCollection(App& app)
{
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Dump/Entries";
asyncResp->res.jsonValue["Name"] = "System Dump Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of System Dump Entries";
getDumpEntryCollection(asyncResp, "System");
});
}
inline void requestRoutesSystemDumpEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
getDumpEntryById(asyncResp, param, "System");
});
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::delete_)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
deleteDumpEntry(asyncResp, param, "system");
});
}
inline void requestRoutesSystemDumpCreate(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/"
"Actions/"
"LogService.CollectDiagnosticData/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{ createDump(asyncResp, req, "System"); });
}
inline void requestRoutesSystemDumpClear(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/"
"Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{ clearDump(asyncResp, "System"); });
}
inline void requestRoutesCrashdumpService(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
.privileges({{"ConfigureManager"}})
.methods(
boost::beast::http::verb::
get)([](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
// Copy over the static data to include the entries added by
// SubRoute
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Crashdump";
asyncResp->res.jsonValue["@odata.type"] =
"#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
asyncResp->res.jsonValue["Id"] = "Oem Crashdump";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"] = {
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
asyncResp->res.jsonValue["Actions"] = {
{"#LogService.ClearLog",
{{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
"Actions/LogService.ClearLog"}}},
{"#LogService.CollectDiagnosticData",
{{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
"Actions/LogService.CollectDiagnosticData"}}}};
});
}
void inline requestRoutesCrashdumpClear(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/Crashdump/Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code ec,
const std::string&) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
},
crashdumpObject, crashdumpPath, deleteAllInterface,
"DeleteAll");
});
}
static void
logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& logID, nlohmann::json& logEntryJson)
{
auto getStoredLogCallback =
[asyncResp, logID, &logEntryJson](
const boost::system::error_code ec,
const std::vector<std::pair<std::string, VariantType>>& params) {
if (ec)
{
BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
if (ec.value() ==
boost::system::linux_error::bad_request_descriptor)
{
messages::resourceNotFound(asyncResp->res, "LogEntry",
logID);
}
else
{
messages::internalError(asyncResp->res);
}
return;
}
std::string timestamp{};
std::string filename{};
std::string logfile{};
parseCrashdumpParameters(params, filename, timestamp, logfile);
if (filename.empty() || timestamp.empty())
{
messages::resourceMissingAtURI(asyncResp->res, logID);
return;
}
std::string crashdumpURI =
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
logID + "/" + filename;
logEntryJson = {{"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
{"@odata.id", "/redfish/v1/Systems/system/"
"LogServices/Crashdump/Entries/" +
logID},
{"Name", "CPU Crashdump"},
{"Id", logID},
{"EntryType", "Oem"},
{"AdditionalDataURI", std::move(crashdumpURI)},
{"DiagnosticDataType", "OEM"},
{"OEMDiagnosticDataType", "PECICrashdump"},
{"Created", std::move(timestamp)}};
};
crow::connections::systemBus->async_method_call(
std::move(getStoredLogCallback), crashdumpObject,
crashdumpPath + std::string("/") + logID,
"org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
}
inline void requestRoutesCrashdumpEntryCollection(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
.privileges({{"ConfigureComponents"}})
.methods(
boost::beast::http::verb::
get)([](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
auto getLogEntriesCallback = [asyncResp](
const boost::system::error_code ec,
const std::vector<std::string>&
resp) {
if (ec)
{
if (ec.value() !=
boost::system::errc::no_such_file_or_directory)
{
BMCWEB_LOG_DEBUG << "failed to get entries ec: "
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of Crashdump Entries";
nlohmann::json& logEntryArray =
asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
std::vector<std::string> logIDs;
// Get the list of log entries and build up an empty array big
// enough to hold them
for (const std::string& objpath : resp)
{
// Get the log ID
std::size_t lastPos = objpath.rfind('/');
if (lastPos == std::string::npos)
{
continue;
}
logIDs.emplace_back(objpath.substr(lastPos + 1));
// Add a space for the log entry to the array
logEntryArray.push_back({});
}
// Now go through and set up async calls to fill in the entries
size_t index = 0;
for (const std::string& logID : logIDs)
{
// Add the log entry to the array
logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]);
}
asyncResp->res.jsonValue["Members@odata.count"] =
logEntryArray.size();
};
crow::connections::systemBus->async_method_call(
std::move(getLogEntriesCallback),
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
std::array<const char*, 1>{crashdumpInterface});
});
}
inline void requestRoutesCrashdumpEntry(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
const std::string& logID = param;
logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
});
}
inline void requestRoutesCrashdumpFile(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/<str>/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& logID, const std::string& fileName) {
auto getStoredLogCallback =
[asyncResp, logID, fileName](
const boost::system::error_code ec,
const std::vector<std::pair<std::string, VariantType>>&
resp) {
if (ec)
{
BMCWEB_LOG_DEBUG << "failed to get log ec: "
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
std::string dbusFilename{};
std::string dbusTimestamp{};
std::string dbusFilepath{};
parseCrashdumpParameters(resp, dbusFilename,
dbusTimestamp, dbusFilepath);
if (dbusFilename.empty() || dbusTimestamp.empty() ||
dbusFilepath.empty())
{
messages::resourceMissingAtURI(asyncResp->res,
fileName);
return;
}
// Verify the file name parameter is correct
if (fileName != dbusFilename)
{
messages::resourceMissingAtURI(asyncResp->res,
fileName);
return;
}
if (!std::filesystem::exists(dbusFilepath))
{
messages::resourceMissingAtURI(asyncResp->res,
fileName);
return;
}
std::ifstream ifs(dbusFilepath, std::ios::in |
std::ios::binary |
std::ios::ate);
std::ifstream::pos_type fileSize = ifs.tellg();
if (fileSize < 0)
{
messages::generalError(asyncResp->res);
return;
}
ifs.seekg(0, std::ios::beg);
auto crashData = std::make_unique<char[]>(
static_cast<unsigned int>(fileSize));
ifs.read(crashData.get(), static_cast<int>(fileSize));
// The cast to std::string is intentional in order to
// use the assign() that applies move mechanics
asyncResp->res.body().assign(
static_cast<std::string>(crashData.get()));
// Configure this to be a file download when accessed
// from a browser
asyncResp->res.addHeader("Content-Disposition",
"attachment");
};
crow::connections::systemBus->async_method_call(
std::move(getStoredLogCallback), crashdumpObject,
crashdumpPath + std::string("/") + logID,
"org.freedesktop.DBus.Properties", "GetAll",
crashdumpInterface);
});
}
inline void requestRoutesCrashdumpCollect(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/"
"Actions/LogService.CollectDiagnosticData/")
.privileges({{"ConfigureComponents"}})
.methods(
boost::beast::http::verb::
post)([](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
std::string diagnosticDataType;
std::string oemDiagnosticDataType;
if (!redfish::json_util::readJson(
req, asyncResp->res, "DiagnosticDataType",
diagnosticDataType, "OEMDiagnosticDataType",
oemDiagnosticDataType))
{
return;
}
if (diagnosticDataType != "OEM")
{
BMCWEB_LOG_ERROR
<< "Only OEM DiagnosticDataType supported for Crashdump";
messages::actionParameterValueFormatError(
asyncResp->res, diagnosticDataType, "DiagnosticDataType",
"CollectDiagnosticData");
return;
}
auto collectCrashdumpCallback = [asyncResp, req](
const boost::system::error_code
ec,
const std::string&) {
if (ec)
{
if (ec.value() ==
boost::system::errc::operation_not_supported)
{
messages::resourceInStandby(asyncResp->res);
}
else if (ec.value() ==
boost::system::errc::device_or_resource_busy)
{
messages::serviceTemporarilyUnavailable(asyncResp->res,
"60");
}
else
{
messages::internalError(asyncResp->res);
}
return;
}
std::shared_ptr<task::TaskData> task =
task::TaskData::createTask(
[](boost::system::error_code err,
sdbusplus::message::message&,
const std::shared_ptr<task::TaskData>& taskData) {
if (!err)
{
taskData->messages.emplace_back(
messages::taskCompletedOK(
std::to_string(taskData->index)));
taskData->state = "Completed";
}
return task::completed;
},
"type='signal',interface='org.freedesktop.DBus."
"Properties',"
"member='PropertiesChanged',arg0namespace='com.intel."
"crashdump'");
task->startTimer(std::chrono::minutes(5));
task->populateResp(asyncResp->res);
task->payload.emplace(req);
};
if (oemDiagnosticDataType == "OnDemand")
{
crow::connections::systemBus->async_method_call(
std::move(collectCrashdumpCallback), crashdumpObject,
crashdumpPath, crashdumpOnDemandInterface,
"GenerateOnDemandLog");
}
else if (oemDiagnosticDataType == "Telemetry")
{
crow::connections::systemBus->async_method_call(
std::move(collectCrashdumpCallback), crashdumpObject,
crashdumpPath, crashdumpTelemetryInterface,
"GenerateTelemetryLog");
}
else
{
BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: "
<< oemDiagnosticDataType;
messages::actionParameterValueFormatError(
asyncResp->res, oemDiagnosticDataType,
"OEMDiagnosticDataType", "CollectDiagnosticData");
return;
}
});
}
/**
* DBusLogServiceActionsClear class supports POST method for ClearLog action.
*/
inline void requestRoutesDBusLogServiceActionsClear(App& app)
{
/**
* Function handles POST method request.
* The Clear Log actions does not require any parameter.The action deletes
* all entries found in the Entries collection for this Log Service.
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
BMCWEB_LOG_DEBUG << "Do delete all entries.";
// Process response from Logging service.
auto respHandler = [asyncResp](
const boost::system::error_code ec) {
BMCWEB_LOG_DEBUG
<< "doClearLog resp_handler callback: Done";
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR << "doClearLog resp_handler got error "
<< ec;
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
asyncResp->res.result(
boost::beast::http::status::no_content);
};
// Make call to Logging service to request Clear Log
crow::connections::systemBus->async_method_call(
respHandler, "xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging",
"xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
});
}
/****************************************************
* Redfish PostCode interfaces
* using DBUS interface: getPostCodesTS
******************************************************/
inline void requestRoutesPostCodesLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue = {
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/PostCodes"},
{"@odata.type", "#LogService.v1_1_0.LogService"},
{"Name", "POST Code Log Service"},
{"Description", "POST Code Log Service"},
{"Id", "BIOS POST Code Log"},
{"OverWritePolicy", "WrapsWhenFull"},
{"Entries",
{{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
"PostCodes/Entries"}}}};
std::pair<std::string, std::string> redfishDateTimeOffset =
crow::utility::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] =
redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
{"target",
"/redfish/v1/Systems/system/LogServices/PostCodes/"
"Actions/LogService.ClearLog"}};
});
}
inline void requestRoutesPostCodesClear(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/PostCodes/Actions/"
"LogService.ClearLog/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
// Make call to post-code service to request clear all
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code ec) {
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR
<< "doClearPostCodes resp_handler got error "
<< ec;
asyncResp->res.result(boost::beast::http::status::
internal_server_error);
messages::internalError(asyncResp->res);
return;
}
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
});
}
static void fillPostCodeEntry(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
const uint16_t bootIndex, const uint64_t codeIndex = 0,
const uint64_t skip = 0, const uint64_t top = 0)
{
// Get the Message from the MessageRegistry
const message_registries::Message* message =
message_registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
uint64_t currentCodeIndex = 0;
nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"];
uint64_t firstCodeTimeUs = 0;
for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
code : postcode)
{
currentCodeIndex++;
std::string postcodeEntryID =
"B" + std::to_string(bootIndex) + "-" +
std::to_string(currentCodeIndex); // 1 based index in EntryID string
uint64_t usecSinceEpoch = code.first;
uint64_t usTimeOffset = 0;
if (1 == currentCodeIndex)
{ // already incremented
firstCodeTimeUs = code.first;
}
else
{
usTimeOffset = code.first - firstCodeTimeUs;
}
// skip if no specific codeIndex is specified and currentCodeIndex does
// not fall between top and skip
if ((codeIndex == 0) &&
(currentCodeIndex <= skip || currentCodeIndex > top))
{
continue;
}
// skip if a specific codeIndex is specified and does not match the
// currentIndex
if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
{
// This is done for simplicity. 1st entry is needed to calculate
// time offset. To improve efficiency, one can get to the entry
// directly (possibly with flatmap's nth method)
continue;
}
// currentCodeIndex is within top and skip or equal to specified code
// index
// Get the Created time from the timestamp
std::string entryTimeStr;
entryTimeStr = crow::utility::getDateTime(
static_cast<std::time_t>(usecSinceEpoch / 1000 / 1000));
// assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
std::ostringstream hexCode;
hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
<< std::get<0>(code.second);
std::ostringstream timeOffsetStr;
// Set Fixed -Point Notation
timeOffsetStr << std::fixed;
// Set precision to 4 digits
timeOffsetStr << std::setprecision(4);
// Add double to stream
timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
std::vector<std::string> messageArgs = {
std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
// Get MessageArgs template from message registry
std::string msg;
if (message != nullptr)
{
msg = message->message;
// fill in this post code value
int i = 0;
for (const std::string& messageArg : messageArgs)
{
std::string argStr = "%" + std::to_string(++i);
size_t argPos = msg.find(argStr);
if (argPos != std::string::npos)
{
msg.replace(argPos, argStr.length(), messageArg);
}
}
}
// Get Severity template from message registry
std::string severity;
if (message != nullptr)
{
severity = message->severity;
}
// add to AsyncResp
logEntryArray.push_back({});
nlohmann::json& bmcLogEntry = logEntryArray.back();
bmcLogEntry = {{"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
"PostCodes/Entries/" +
postcodeEntryID},
{"Name", "POST Code Log Entry"},
{"Id", postcodeEntryID},
{"Message", std::move(msg)},
{"MessageId", "OpenBMC.0.2.BIOSPOSTCode"},
{"MessageArgs", std::move(messageArgs)},
{"EntryType", "Event"},
{"Severity", std::move(severity)},
{"Created", entryTimeStr}};
if (std::get<std::vector<uint8_t>>(code.second).size())
{
bmcLogEntry["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" +
postcodeEntryID + "/attachment";
}
}
}
static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const uint16_t bootIndex,
const uint64_t codeIndex)
{
crow::connections::systemBus->async_method_call(
[aResp, bootIndex,
codeIndex](const boost::system::error_code ec,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
postcode) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
messages::internalError(aResp->res);
return;
}
// skip the empty postcode boots
if (postcode.empty())
{
return;
}
fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex);
aResp->res.jsonValue["Members@odata.count"] =
aResp->res.jsonValue["Members"].size();
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
bootIndex);
}
static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const uint16_t bootIndex,
const uint16_t bootCount,
const uint64_t entryCount, const uint64_t skip,
const uint64_t top)
{
crow::connections::systemBus->async_method_call(
[aResp, bootIndex, bootCount, entryCount, skip,
top](const boost::system::error_code ec,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
postcode) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
messages::internalError(aResp->res);
return;
}
uint64_t endCount = entryCount;
if (!postcode.empty())
{
endCount = entryCount + postcode.size();
if ((skip < endCount) && ((top + skip) > entryCount))
{
uint64_t thisBootSkip =
std::max(skip, entryCount) - entryCount;
uint64_t thisBootTop =
std::min(top + skip, endCount) - entryCount;
fillPostCodeEntry(aResp, postcode, bootIndex, 0,
thisBootSkip, thisBootTop);
}
aResp->res.jsonValue["Members@odata.count"] = endCount;
}
// continue to previous bootIndex
if (bootIndex < bootCount)
{
getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1),
bootCount, endCount, skip, top);
}
else
{
aResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/"
"Entries?$skip=" +
std::to_string(skip + top);
}
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
bootIndex);
}
static void
getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const uint64_t skip, const uint64_t top)
{
uint64_t entryCount = 0;
crow::connections::systemBus->async_method_call(
[aResp, entryCount, skip,
top](const boost::system::error_code ec,
const std::variant<uint16_t>& bootCount) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(aResp->res);
return;
}
auto pVal = std::get_if<uint16_t>(&bootCount);
if (pVal)
{
getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top);
}
else
{
BMCWEB_LOG_DEBUG << "Post code boot index failed.";
}
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"org.freedesktop.DBus.Properties", "Get",
"xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount");
}
inline void requestRoutesPostCodesEntryCollection(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of POST Code Log Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
uint64_t skip = 0;
uint64_t top = maxEntriesPerPage; // Show max entries by default
if (!getSkipParam(asyncResp, req, skip))
{
return;
}
if (!getTopParam(asyncResp, req, top))
{
return;
}
getCurrentBootNumber(asyncResp, skip, top);
});
}
inline void requestRoutesPostCodesEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& targetID) {
size_t bootPos = targetID.find('B');
if (bootPos == std::string::npos)
{
// Requested ID was not found
messages::resourceMissingAtURI(asyncResp->res, targetID);
return;
}
std::string_view bootIndexStr(targetID);
bootIndexStr.remove_prefix(bootPos + 1);
uint16_t bootIndex = 0;
uint64_t codeIndex = 0;
size_t dashPos = bootIndexStr.find('-');
if (dashPos == std::string::npos)
{
return;
}
std::string_view codeIndexStr(bootIndexStr);
bootIndexStr.remove_suffix(dashPos);
codeIndexStr.remove_prefix(dashPos + 1);
bootIndex = static_cast<uint16_t>(
strtoul(std::string(bootIndexStr).c_str(), nullptr, 0));
codeIndex =
strtoul(std::string(codeIndexStr).c_str(), nullptr, 0);
if (bootIndex == 0 || codeIndex == 0)
{
BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string "
<< targetID;
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntry.v1_4_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/"
"Entries";
asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of POST Code Log Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
getPostCodeForEntry(asyncResp, bootIndex, codeIndex);
});
}
} // namespace redfish