Merge "Use s.c_str() in log messages"
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..1719d45
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,46 @@
+How to use this list:
+ Find the most specific section entry (described below) that matches where
+ your change lives and add the reviewers (R) and maintainers (M) as
+ reviewers. You can use the same method to track down who knows a particular
+ code base best.
+
+ Your change/query may span multiple entries; that is okay.
+
+ If you do not find an entry that describes your request at all, someone
+ forgot to update this list; please at least file an issue or send an email
+ to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+ Section entries are structured according to the following scheme:
+
+ X: NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+ X: ...
+ .
+ .
+ .
+
+ Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+ organization; FILE_PATH is a file path within the repository, possibly with
+ wildcards; X is a tag of one of the following types:
+
+ M: Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ if omitted from an entry, assume one of the maintainers from the
+ MAINTAINERS entry.
+ R: Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ these people are to be added as reviewers for a change matching the repo
+ path.
+ F: Denotes forked from an external repository; has fields URL.
+
+ Line comments are to be denoted "# SOME COMMENT" (typical shell style
+ comment); it is important to follow the correct syntax and semantics as we
+ may want to use automated tools with this file in the future.
+
+ A change cannot be added to an OpenBMC repository without a MAINTAINER's
+ approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M: Deepak Kodihalli <dkodihal@linux.vnet.ibm.com> <dkodihal!>
+M: Matt Spinler <spinler@us.ibm.com> <mspinler[m]!>
diff --git a/Makefile.am b/Makefile.am
index 5cc1a1f..1b2bbcd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,7 @@
sbin_PROGRAMS = ibm-log-manager
ibm_log_manager_SOURCES = \
+ callout.cpp \
dbus.cpp \
main.cpp \
manager.cpp \
diff --git a/callout.cpp b/callout.cpp
new file mode 100644
index 0000000..7c0310e
--- /dev/null
+++ b/callout.cpp
@@ -0,0 +1,185 @@
+/** Copyright © 2018 IBM 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.
+ */
+#include <cereal/archives/binary.hpp>
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/types/tuple.hpp>
+#include <experimental/filesystem>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include "callout.hpp"
+#include "config.h"
+#include "dbus.hpp"
+
+CEREAL_CLASS_VERSION(ibm::logging::Callout, CALLOUT_CLASS_VERSION);
+
+namespace ibm
+{
+namespace logging
+{
+
+using namespace phosphor::logging;
+
+/**
+ * Function required by Cereal for saving data
+ *
+ * @param[in] archive - the Cereal archive object
+ * @param[in] callout - the object to save
+ * @param[in] version - the version of the persisted data
+ */
+template <class Archive>
+void save(Archive& archive, const Callout& callout, const std::uint32_t version)
+{
+ archive(callout.id(), callout.ts(), callout.path(), callout.buildDate(),
+ callout.manufacturer(), callout.model(), callout.partNumber(),
+ callout.serialNumber());
+}
+
+/**
+ * Function required by Cereal for restoring data into an object
+ *
+ * @param[in] archive - the Cereal archive object
+ * @param[in] callout - the callout object to restore
+ * @param[in] version - the version of the persisted data
+ */
+template <class Archive>
+void load(Archive& archive, Callout& callout, const std::uint32_t version)
+{
+ size_t id;
+ uint64_t timestamp;
+ std::string inventoryPath;
+ std::string build;
+ std::string mfgr;
+ std::string model;
+ std::string pn;
+ std::string sn;
+
+ archive(id, timestamp, inventoryPath, build, mfgr, model, pn, sn);
+
+ callout.id(id);
+ callout.ts(timestamp);
+ callout.path(inventoryPath);
+ callout.buildDate(build);
+ callout.manufacturer(mfgr);
+ callout.model(model);
+ callout.partNumber(pn);
+ callout.serialNumber(sn);
+}
+
+Callout::Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+ size_t id, uint64_t timestamp) :
+ CalloutObject(bus, objectPath.c_str(), true),
+ entryID(id), timestamp(timestamp)
+{
+}
+
+Callout::Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+ const std::string& inventoryPath, size_t id,
+ uint64_t timestamp, const DbusPropertyMap& properties) :
+ CalloutObject(bus, objectPath.c_str(), true),
+ entryID(id), timestamp(timestamp)
+{
+ path(inventoryPath);
+
+ auto it = properties.find("BuildDate");
+ if (it != properties.end())
+ {
+ buildDate(it->second.get<std::string>());
+ }
+
+ it = properties.find("Manufacturer");
+ if (it != properties.end())
+ {
+ manufacturer(it->second.get<std::string>());
+ }
+
+ it = properties.find("Model");
+ if (it != properties.end())
+ {
+ model(it->second.get<std::string>());
+ }
+
+ it = properties.find("PartNumber");
+ if (it != properties.end())
+ {
+ partNumber(it->second.get<std::string>());
+ }
+
+ it = properties.find("SerialNumber");
+ if (it != properties.end())
+ {
+ serialNumber(it->second.get<std::string>());
+ }
+
+ emit_object_added();
+}
+
+void Callout::serialize(const fs::path& dir)
+{
+ auto path = getFilePath(dir);
+ std::ofstream stream(path.c_str(), std::ios::binary);
+ cereal::BinaryOutputArchive oarchive(stream);
+
+ oarchive(*this);
+}
+
+bool Callout::deserialize(const fs::path& dir)
+{
+ auto path = getFilePath(dir);
+
+ if (!fs::exists(path))
+ {
+ return false;
+ }
+
+ // Save the current ID and timestamp and then use them after
+ // deserialization to check that the data we are restoring
+ // is for the correct error log.
+
+ auto originalID = entryID;
+ auto originalTS = timestamp;
+
+ try
+ {
+ std::ifstream stream(path.c_str(), std::ios::binary);
+ cereal::BinaryInputArchive iarchive(stream);
+
+ iarchive(*this);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>(e.what());
+ log<level::ERR>("Failed trying to restore a Callout object",
+ entry("PATH=%s", path.c_str()));
+ fs::remove(path);
+ return false;
+ }
+
+ if ((entryID != originalID) || (timestamp != originalTS))
+ {
+ log<level::INFO>(
+ "Timestamp or ID mismatch in persisted Callout. Discarding",
+ entry("PATH=%s", path.c_str()), entry("PERSISTED_ID=%lu", entryID),
+ entry("EXPECTED_ID=%lu", originalID),
+ entry("PERSISTED_TS=%llu", timestamp),
+ entry("EXPECTED_TS=%llu", originalTS));
+ fs::remove(path);
+ return false;
+ }
+
+ return true;
+}
+}
+}
diff --git a/callout.hpp b/callout.hpp
new file mode 100644
index 0000000..b1e3b53
--- /dev/null
+++ b/callout.hpp
@@ -0,0 +1,154 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include "dbus.hpp"
+#include "interfaces.hpp"
+
+namespace ibm
+{
+namespace logging
+{
+
+namespace fs = std::experimental::filesystem;
+
+/**
+ * @class Callout
+ *
+ * This class provides information about a callout by utilizing the
+ * xyz.openbmc_project.Inventory.Decorator.Asset and
+ * xyz.openbmc_project.Common.ObjectPath interfaces.
+ *
+ * It also has the ability to persist and restore its data.
+ */
+class Callout : public CalloutObject
+{
+ public:
+ Callout() = delete;
+ Callout(const Callout&) = delete;
+ Callout& operator=(const Callout&) = delete;
+ Callout(Callout&&) = default;
+ Callout& operator=(Callout&&) = default;
+ ~Callout() = default;
+
+ /**
+ * Constructor
+ *
+ * Populates the Asset D-Bus properties with data from the property map.
+ *
+ * @param[in] bus - D-Bus object
+ * @param[in] objectPath - object path
+ * @param[in] inventoryPath - inventory path of the callout
+ * @param[in] id - which callout this is
+ * @param[in] timestamp - timestamp when the log was created
+ * @param[in] properties - the properties for the Asset interface.
+ */
+ Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+ const std::string& inventoryPath, size_t id, uint64_t timestamp,
+ const DbusPropertyMap& properties);
+ /**
+ * Constructor
+ *
+ * This version is for when the object is being restored and does
+ * not take the properties map.
+ *
+ * @param[in] bus - D-Bus object
+ * @param[in] objectPath - object path
+ * @param[in] id - which callout this is
+ * @param[in] timestamp - timestamp when the log was created
+ * @param[in] properties - the properties for the Asset interface.
+ */
+ Callout(sdbusplus::bus::bus& bus, const std::string& objectPath, size_t id,
+ uint64_t timestamp);
+
+ /**
+ * Returns the callout ID
+ *
+ * @return id - the ID
+ */
+ inline auto id() const
+ {
+ return entryID;
+ }
+
+ /**
+ * Sets the callout ID
+ *
+ * @param[in] id - the ID
+ */
+ inline void id(uint32_t id)
+ {
+ entryID = id;
+ }
+
+ /**
+ * Returns the timestamp
+ *
+ * @return timestamp
+ */
+ inline auto ts() const
+ {
+ return timestamp;
+ }
+
+ /**
+ * Sets the timestamp
+ *
+ * @param[in] ts - the timestamp
+ */
+ inline void ts(uint64_t ts)
+ {
+ timestamp = ts;
+ }
+
+ /**
+ * Serializes the class instance into a file in the
+ * directory passed in. The filename will match the
+ * ID value passed into the constructor.
+ *
+ * @param[in] - the directory to save the file in.
+ */
+ void serialize(const fs::path& dir);
+
+ /**
+ * Loads the class members in from a file written by a previous
+ * call to serialize(). The filename it uses is the ID
+ * value passed into the constructor in the directory
+ * passed to this function.
+ *
+ * @param[in] dir - the directory to look for the file in
+ *
+ * @return bool - true if the deserialization was successful,
+ * false if it wasn't
+ */
+ bool deserialize(const fs::path& dir);
+
+ private:
+ /**
+ * Returns the fully qualified filename to use for the serialization
+ * data. The file is the ID value, like "0", in the base directory
+ * passed in.
+ *
+ * @param[in] baseDir - the directory the file will be in
+ *
+ * @return path - the filename
+ */
+ inline auto getFilePath(const fs::path& baseDir)
+ {
+ return baseDir / std::to_string(entryID);
+ }
+
+ /**
+ * The unique identifier for the callout, as error logs can have
+ * multiple callouts. They start at 0.
+ */
+ size_t entryID;
+
+ /**
+ * The timestamp of when the error log was created.
+ * Used for ensuring the callout data is being restored for
+ * the correct error log.
+ */
+ uint64_t timestamp;
+};
+}
+}
diff --git a/configure.ac b/configure.ac
index 07abbe0..2074692 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,6 +22,9 @@
AC_CHECK_HEADER(nlohmann/json.hpp, ,
[AC_MSG_ERROR([Could not find nlohmann/json.hpp... nlohmann/json package required])])
+AC_CHECK_HEADER(cereal/archives/binary.hpp, ,
+ [AC_MSG_ERROR([Could not find cereal/archives/binary.hpp... cereal package required])])
+
# Checks for typedefs, structures, and compiler characteristics.
AX_CXX_COMPILE_STDCXX_14([noext])
AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
@@ -73,6 +76,10 @@
[The xyz logging busname])
AC_DEFINE(IBM_LOGGING_BUSNAME, "com.ibm.Logging",
[The IBM log manager DBus busname to own])
+AC_DEFINE(ASSOC_IFACE, "org.openbmc.Associations",
+ [The associations interface])
+AC_DEFINE(ASSET_IFACE, "xyz.openbmc_project.Inventory.Decorator.Asset",
+ [The asset interface])
AC_DEFINE(DEFAULT_POLICY_EID, "None",
[The default event ID to use])
@@ -83,5 +90,18 @@
AS_IF([test "x$POLICY_JSON_PATH" == "x"], [POLICY_JSON_PATH="/usr/share/ibm-logging/policy.json"])
AC_DEFINE_UNQUOTED([POLICY_JSON_PATH], ["$POLICY_JSON_PATH"], [The path to the policy json file on the BMC])
+AC_ARG_VAR(ERRLOG_PERSIST_PATH, [Path to save errors in])
+AS_IF([test "x$ERRLOG_PERSIST_PATH" == "x"], \
+ [ERRLOG_PERSIST_PATH="/var/lib/ibm-logging/errors"])
+AC_DEFINE_UNQUOTED([ERRLOG_PERSIST_PATH], ["$ERRLOG_PERSIST_PATH"], \
+ [Path to save errors in])
+
+AC_ARG_VAR(CALLOUT_CLASS_VERSION,
+ [Callout class version to register with Cereal])
+AS_IF([test "x$CALLOUT_CLASS_VERSION" == "x"],
+ [CALLOUT_CLASS_VERSION=1])
+AC_DEFINE_UNQUOTED([CALLOUT_CLASS_VERSION], [$CALLOUT_CLASS_VERSION],
+ [Callout Class version to register with Cereal])
+
AC_CONFIG_FILES([Makefile test/Makefile])
AC_OUTPUT
diff --git a/dbus.cpp b/dbus.cpp
index 17ad210..06b0078 100644
--- a/dbus.cpp
+++ b/dbus.cpp
@@ -21,6 +21,13 @@
namespace logging
{
+constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_IFACE = "xyz.openbmc_project.ObjectMapper";
+constexpr auto PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
+
+using namespace phosphor::logging;
+
ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
const std::string& service,
const std::string& objPath)
@@ -35,8 +42,8 @@
if (reply.is_method_error())
{
- using namespace phosphor::logging;
log<level::ERR>("Failed to get managed objects",
+ entry("SERVICE=%s", service.c_str()),
entry("PATH=%s", objPath.c_str()));
}
else
@@ -46,5 +53,81 @@
return interfaces;
}
+
+DbusPropertyMap getAllProperties(sdbusplus::bus::bus& bus,
+ const std::string& service,
+ const std::string& objPath,
+ const std::string& interface)
+{
+ DbusPropertyMap properties;
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ PROPERTY_IFACE, "GetAll");
+ method.append(interface);
+ auto reply = bus.call(method);
+
+ if (reply.is_method_error())
+ {
+ log<level::ERR>("Failed to get all properties",
+ entry("SERVICE=%s", service.c_str()),
+ entry("PATH=%s", objPath.c_str()),
+ entry("INTERFACE=%s", interface.c_str()));
+ }
+ else
+ {
+ reply.read(properties);
+ }
+
+ return properties;
+}
+
+DbusSubtree getSubtree(sdbusplus::bus::bus& bus, const std::string& root,
+ size_t depth, const std::string& interface)
+{
+ DbusSubtree tree;
+
+ auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, MAPPER_IFACE,
+ "GetSubTree");
+ method.append(root);
+ method.append(depth);
+ method.append(std::vector<std::string>({interface}));
+ auto reply = bus.call(method);
+
+ if (reply.is_method_error())
+ {
+ log<level::ERR>("Failed to get subtree", entry("ROOT=%s", root.c_str()),
+ entry("INTERFACE=%s", interface.c_str()));
+ }
+ else
+ {
+ reply.read(tree);
+ }
+
+ return tree;
+}
+
+DbusService getService(const std::string& objPath, const std::string& interface,
+ const DbusSubtree& tree)
+{
+ DbusService service;
+
+ auto services = tree.find(objPath);
+ if (services != tree.end())
+ {
+ auto s = std::find_if(services->second.begin(), services->second.end(),
+ [&interface](const auto& entry) {
+ auto i =
+ std::find(entry.second.begin(),
+ entry.second.end(), interface);
+ return i != entry.second.end();
+ });
+ if (s != services->second.end())
+ {
+ service = s->first;
+ }
+ }
+
+ return service;
+}
}
}
diff --git a/dbus.hpp b/dbus.hpp
index 38e62bd..c572d99 100644
--- a/dbus.hpp
+++ b/dbus.hpp
@@ -12,8 +12,18 @@
using DbusInterface = std::string;
using DbusProperty = std::string;
+using DbusService = std::string;
+using DbusPath = std::string;
+
+static constexpr auto forwardPos = 0;
+static constexpr auto reversePos = 1;
+static constexpr auto endpointPos = 2;
+using AssociationsPropertyType =
+ std::vector<std::tuple<std::string, std::string, std::string>>;
+
using Value = sdbusplus::message::variant<bool, uint32_t, uint64_t, std::string,
- std::vector<std::string>>;
+ std::vector<std::string>,
+ AssociationsPropertyType>;
using DbusPropertyMap = std::map<DbusProperty, Value>;
using DbusInterfaceMap = std::map<DbusInterface, DbusPropertyMap>;
@@ -22,8 +32,71 @@
using ObjectValueTree =
std::map<sdbusplus::message::object_path, DbusInterfaceMap>;
+using DbusSubtree =
+ std::map<DbusPath, std::map<DbusService, DbusInterfaceList>>;
+
+/**
+ * Returns the managed objects for an object path and service
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] service - the D-Bus service name
+ * @param[in] objPath - the D-Bus object path
+ *
+ * @return ObjectValueTree - A map of object paths to their
+ * interfaces and properties.
+ */
ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
const std::string& service,
const std::string& objPath);
+
+/**
+ * Returns the subtree for a root, depth, and interface.
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] root - the point from which to provide results
+ * @param[in] depth - the number of path elements to descend
+ *
+ * @return DbusSubtree - A map of object paths to their
+ * services and interfaces.
+ */
+DbusSubtree getSubtree(sdbusplus::bus::bus& bus, const std::string& root,
+ size_t depth, const std::string& interface);
+
+/**
+ * Get the D-Bus service name for the object path and interface from
+ * the data returned from a GetSubTree call.
+ *
+ * Returns an empty string if the service can't be found.
+ *
+ * @param[in] objPath - the D-Bus object path
+ * @param[in] interface - the D-Bus interface name
+ * @param[in] tree - the D-Bus GetSubTree response
+ *
+ * @return string - the service name
+ */
+DbusService getService(const std::string& objPath, const std::string& interface,
+ const DbusSubtree& tree);
+
+/**
+ * Returns all properties on a particular interface on a
+ * particular D-Bus object.
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] service - the D-Bus service name
+ * @param[in] objPath - the D-Bus object path
+ * @param[in] interface - the D-Bus interface name
+ *
+ * @return DbusPropertyMap - The map of property names to values
+ */
+DbusPropertyMap getAllProperties(sdbusplus::bus::bus& bus,
+ const std::string& service,
+ const std::string& objPath,
+ const std::string& interface);
}
}
diff --git a/interfaces.hpp b/interfaces.hpp
index 0dfa668..6c7651f 100644
--- a/interfaces.hpp
+++ b/interfaces.hpp
@@ -1,6 +1,8 @@
#pragma once
#include <com/ibm/Logging/Policy/server.hpp>
+#include <xyz/openbmc_project/Common/ObjectPath/server.hpp>
+#include <xyz/openbmc_project/Inventory/Decorator/Asset/server.hpp>
namespace ibm
{
@@ -10,11 +12,19 @@
template <typename... T>
using ServerObject = typename sdbusplus::server::object::object<T...>;
+using ObjectPathInterface =
+ sdbusplus::xyz::openbmc_project::Common::server::ObjectPath;
+
+using CalloutInterface =
+ sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Asset;
+using CalloutObject = ServerObject<CalloutInterface, ObjectPathInterface>;
+
using PolicyInterface = sdbusplus::com::ibm::Logging::server::Policy;
using PolicyObject = ServerObject<PolicyInterface>;
enum class InterfaceType
{
+ CALLOUT,
POLICY
};
}
diff --git a/manager.cpp b/manager.cpp
index 276a5a5..2503e93 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <phosphor-logging/log.hpp>
+#include "callout.hpp"
#include "config.h"
#include "manager.hpp"
#include "policy_find.hpp"
@@ -22,6 +24,8 @@
namespace logging
{
+namespace fs = std::experimental::filesystem;
+
Manager::Manager(sdbusplus::bus::bus& bus) :
bus(bus),
addMatch(bus,
@@ -50,26 +54,47 @@
{
const auto& interfaces = object.second;
- auto propertyMap = std::find_if(
- interfaces.begin(), interfaces.end(),
- [](const auto& i) { return i.first == LOGGING_IFACE; });
+ auto propertyMap = interfaces.find(LOGGING_IFACE);
if (propertyMap != interfaces.end())
{
- create(object.first, propertyMap->second);
+ createWithRestore(object.first, interfaces);
}
}
}
-void Manager::create(const std::string& objectPath,
- const DbusPropertyMap& properties)
+void Manager::createWithRestore(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces)
{
+ createObject(objectPath, interfaces);
+ restoreCalloutObjects(objectPath, interfaces);
+}
+
+void Manager::create(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces)
+{
+ createObject(objectPath, interfaces);
+
+ createCalloutObjects(objectPath, interfaces);
+}
+
+void Manager::createObject(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces)
+{
#ifdef USE_POLICY_INTERFACE
- createPolicyInterface(objectPath, properties);
+ auto logInterface = interfaces.find(LOGGING_IFACE);
+ createPolicyInterface(objectPath, logInterface->second);
#endif
}
+void Manager::erase(EntryID id)
+{
+ fs::remove_all(getSaveDir(id));
+ childEntries.erase(id);
+ entries.erase(id);
+}
+
void Manager::addInterface(const std::string& objectPath, InterfaceType type,
std::experimental::any& object)
{
@@ -88,6 +113,40 @@
}
}
+void Manager::addChildInterface(const std::string& objectPath,
+ InterfaceType type,
+ std::experimental::any& object)
+{
+ auto id = getEntryID(objectPath);
+ auto entry = childEntries.find(id);
+
+ // childEntries is:
+ // A map of error log entry IDs to:
+ // a map of interface types to:
+ // a vector of interface objects
+
+ if (entry == childEntries.end())
+ {
+ ObjectList objects{object};
+ InterfaceMapMulti interfaces;
+ interfaces.emplace(type, std::move(objects));
+ childEntries.emplace(id, std::move(interfaces));
+ }
+ else
+ {
+ auto i = entry->second.find(type);
+ if (i == entry->second.end())
+ {
+ ObjectList objects{objects};
+ entry->second.emplace(type, objects);
+ }
+ else
+ {
+ i->second.emplace_back(object);
+ }
+ }
+}
+
#ifdef USE_POLICY_INTERFACE
void Manager::createPolicyInterface(const std::string& objectPath,
const DbusPropertyMap& properties)
@@ -107,6 +166,115 @@
}
#endif
+void Manager::createCalloutObjects(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces)
+{
+ // Use the associations property in the org.openbmc.Associations
+ // interface to find any callouts. Then grab all properties on
+ // the Asset interface for that object in the inventory to use
+ // in our callout objects.
+
+ auto associations = interfaces.find(ASSOC_IFACE);
+ if (associations == interfaces.end())
+ {
+ return;
+ }
+
+ const auto& properties = associations->second;
+ auto assocProperty = properties.find("associations");
+ auto assocValue = assocProperty->second.get<AssociationsPropertyType>();
+
+ auto id = getEntryID(objectPath);
+ auto calloutNum = 0;
+ DbusSubtree subtree;
+
+ for (const auto& association : assocValue)
+ {
+ if (std::get<forwardPos>(association) != "callout")
+ {
+ continue;
+ }
+
+ auto callout = std::get<endpointPos>(association);
+
+ if (subtree.empty())
+ {
+ subtree = getSubtree(bus, "/", 0, ASSET_IFACE);
+ if (subtree.empty())
+ {
+ break;
+ }
+ }
+
+ auto service = getService(callout, ASSET_IFACE, subtree);
+ if (service.empty())
+ {
+ continue;
+ }
+
+ auto properties = getAllProperties(bus, service, callout, ASSET_IFACE);
+ if (properties.empty())
+ {
+ continue;
+ }
+
+ auto calloutPath = getCalloutObjectPath(objectPath, calloutNum);
+
+ auto object =
+ std::make_shared<Callout>(bus, calloutPath, callout, calloutNum,
+ getLogTimestamp(interfaces), properties);
+
+ auto dir = getCalloutSaveDir(id);
+ if (!fs::exists(dir))
+ {
+ fs::create_directories(dir);
+ }
+ object->serialize(dir);
+
+ std::experimental::any anyObject = object;
+ addChildInterface(objectPath, InterfaceType::CALLOUT, anyObject);
+ calloutNum++;
+ }
+}
+
+void Manager::restoreCalloutObjects(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces)
+{
+ auto saveDir = getCalloutSaveDir(getEntryID(objectPath));
+
+ if (!fs::exists(saveDir))
+ {
+ return;
+ }
+
+ size_t id;
+ for (auto& f : fs::directory_iterator(saveDir))
+ {
+ try
+ {
+ id = std::stoul(f.path().filename());
+ }
+ catch (std::exception& e)
+ {
+ using namespace phosphor::logging;
+ log<level::ERR>("Invalid IBM logging callout save file. Deleting",
+ entry("FILE=%s", f.path().c_str()));
+ fs::remove(f.path());
+ continue;
+ }
+
+ auto path = getCalloutObjectPath(objectPath, id);
+ auto callout = std::make_shared<Callout>(bus, path, id,
+ getLogTimestamp(interfaces));
+ if (callout->deserialize(saveDir))
+ {
+ callout->emit_object_added();
+ std::experimental::any anyObject = callout;
+ addChildInterface(objectPath, InterfaceType::CALLOUT, anyObject);
+ }
+ }
+}
+
void Manager::interfaceAdded(sdbusplus::message::message& msg)
{
sdbusplus::message::object_path path;
@@ -116,16 +284,43 @@
// Find the Logging.Entry interface with all of its properties
// to pass to create().
- auto propertyMap =
- std::find_if(interfaces.begin(), interfaces.end(),
- [](const auto& i) { return i.first == LOGGING_IFACE; });
-
- if (propertyMap != interfaces.end())
+ if (interfaces.find(LOGGING_IFACE) != interfaces.end())
{
- create(path, propertyMap->second);
+ create(path, interfaces);
}
}
+uint64_t Manager::getLogTimestamp(const DbusInterfaceMap& interfaces)
+{
+ auto interface = interfaces.find(LOGGING_IFACE);
+ if (interface != interfaces.end())
+ {
+ auto property = interface->second.find("Timestamp");
+ if (property != interface->second.end())
+ {
+ return property->second.get<uint64_t>();
+ }
+ }
+
+ return 0;
+}
+
+fs::path Manager::getSaveDir(EntryID id)
+{
+ return fs::path{ERRLOG_PERSIST_PATH} / std::to_string(id);
+}
+
+fs::path Manager::getCalloutSaveDir(EntryID id)
+{
+ return getSaveDir(id) / "callouts";
+}
+
+std::string Manager::getCalloutObjectPath(const std::string& objectPath,
+ uint32_t calloutNum)
+{
+ return fs::path{objectPath} / "callouts" / std::to_string(calloutNum);
+}
+
void Manager::interfaceRemoved(sdbusplus::message::message& msg)
{
sdbusplus::message::object_path path;
@@ -140,13 +335,7 @@
if (i != interfaces.end())
{
- auto id = getEntryID(path);
-
- auto entry = entries.find(id);
- if (entry != entries.end())
- {
- entries.erase(entry);
- }
+ erase(getEntryID(path));
}
}
}
diff --git a/manager.hpp b/manager.hpp
index ca5e16b..39cf958 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -43,6 +43,22 @@
explicit Manager(sdbusplus::bus::bus& bus);
private:
+ using EntryID = uint32_t;
+ using InterfaceMap = std::map<InterfaceType, std::experimental::any>;
+ using EntryMap = std::map<EntryID, InterfaceMap>;
+
+ using ObjectList = std::vector<std::experimental::any>;
+ using InterfaceMapMulti = std::map<InterfaceType, ObjectList>;
+ using EntryMapMulti = std::map<EntryID, InterfaceMapMulti>;
+
+ /**
+ * Deletes the entry and any child entries with
+ * the specified ID.
+ *
+ * @param[in] id - the entry ID
+ */
+ void erase(EntryID id);
+
/**
* The callback for an interfaces added signal
*
@@ -70,14 +86,83 @@
void createAll();
/**
- * Creates the IBM interface(s) for a single error log.
+ * Creates the IBM interface(s) for a single new error log.
+ *
+ * Any interfaces that require serialization will be created
+ * and serialized here.
*
* @param[in] objectPath - object path of the error log
- * @param[in] properties - the xyz.openbmc_project.Logging.Entry
- * properties
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log
*/
void create(const std::string& objectPath,
- const DbusPropertyMap& properties);
+ const DbusInterfaceMap& interfaces);
+
+ /**
+ * Creates the IBM interface(s) for a single error log after
+ * the application is restarted.
+ *
+ * Interfaces that were persisted will be restored from their
+ * previously saved filesystem data.
+ *
+ * @param[in] objectPath - object path of the error log
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log
+ */
+ void createWithRestore(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces);
+
+ /**
+ * Creates the IBM interfaces for a single error log that
+ * do not persist across app restarts.
+ *
+ * @param[in] objectPath - object path of the error log
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log
+ */
+ void createObject(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces);
+
+ /**
+ * Returns the error log timestamp property value from
+ * the passed in map of all interfaces and property names/values
+ * on an error log D-Bus object.
+ *
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log.
+ *
+ * @return uint64_t - the timestamp
+ */
+ uint64_t getLogTimestamp(const DbusInterfaceMap& interfaces);
+
+ /**
+ * Returns the filesystem directory to use for persisting
+ * information about a particular error log.
+ *
+ * @param[in] id - the error log ID
+ * @return path - the directory path
+ */
+ std::experimental::filesystem::path getSaveDir(EntryID id);
+
+ /**
+ * Returns the directory to use to save the callout information in
+ *
+ * @param[in] id - the error log ID
+ *
+ * @return path - the directory path
+ */
+ std::experimental::filesystem::path getCalloutSaveDir(EntryID id);
+
+ /**
+ * Returns the D-Bus object path to use for a callout D-Bus object.
+ *
+ * @param[in] objectPath - the object path for the error log
+ * @param[in] calloutNum - the callout instance number
+ *
+ * @return path - the object path to use for a callout object
+ */
+ std::string getCalloutObjectPath(const std::string& objectPath,
+ uint32_t calloutNum);
/**
* Creates the IBM policy interface for a single error log
@@ -93,6 +178,37 @@
#endif
/**
+ * Creates D-Bus objects for any callouts in an error log
+ * that map to an inventory object with an Asset interface.
+ *
+ * The created object will also host the Asset interface.
+ *
+ * A callout object path would look like:
+ * /xyz/openbmc_project/logging/entry/5/callouts/0.
+ *
+ * Any objects created are serialized so the asset information
+ * can always be restored.
+ *
+ * @param[in] objectPath - object path of the error log
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log.
+ */
+ void createCalloutObjects(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces);
+
+ /**
+ * Restores callout objects for a particular error log that
+ * have previously been saved by reading their data out of
+ * the filesystem using Cereal.
+ *
+ * @param[in] objectPath - object path of the error log
+ * @param[in] interfaces - map of all interfaces and properties
+ * on a phosphor-logging error log.
+ */
+ void restoreCalloutObjects(const std::string& objectPath,
+ const DbusInterfaceMap& interfaces);
+
+ /**
* Returns the entry ID for a log
*
* @param[in] objectPath - the object path of the log
@@ -116,6 +232,21 @@
std::experimental::any& object);
/**
+ * Adds an interface to a child object, which is an object that
+ * relates to the main ...logging/entry/X object but has a different path.
+ * The object is stored in the childEntries map.
+ *
+ * There can be multiple instances of a child object per type per
+ * logging object.
+ *
+ * @param[in] objectPath - the object path of the log
+ * @param[in] type - the interface type being added.
+ * @param[in] object - the interface object
+ */
+ void addChildInterface(const std::string& objectPath, InterfaceType type,
+ std::experimental::any& object);
+
+ /**
* The sdbusplus bus object
*/
sdbusplus::bus::bus& bus;
@@ -130,16 +261,23 @@
*/
sdbusplus::bus::match_t removeMatch;
- using EntryID = uint32_t;
- using InterfaceMap = std::map<InterfaceType, std::experimental::any>;
- using EntryMap = std::map<EntryID, InterfaceMap>;
-
/**
* A map of the error log IDs to their IBM interface objects.
* There may be multiple interfaces per ID.
*/
EntryMap entries;
+ /**
+ * A map of the error log IDs to their interface objects which
+ * are children of the logging objects.
+ *
+ * These objects have the same lifespan as their parent objects.
+ *
+ * There may be multiple interfaces per ID, and also multiple
+ * interface instances per interface type.
+ */
+ EntryMapMulti childEntries;
+
#ifdef USE_POLICY_INTERFACE
/**
* The class the wraps the IBM error logging policy table.
diff --git a/test/Makefile.am b/test/Makefile.am
index a699696..c1cce92 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -2,16 +2,45 @@
TESTS = $(check_PROGRAMS)
-check_PROGRAMS = test_policy
-test_policy_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
+check_PROGRAMS = test_policy test_callout
-test_policy_CXXFLAGS = $(PTHREAD_CFLAGS) $(SDBUSPLUS_CFLAGS)
-test_policy_LDFLAGS = -lgtest_main -lgtest \
- $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS)
+test_cppflags = \
+ -Igtest \
+ $(GTEST_CPPFLAGS) \
+ $(AM_CPPFLAGS)
+test_cxxflags = \
+ $(PTHREAD_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(IBM_DBUS_INTERFACES_CFLAGS) \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
+
+test_ldflags = \
+ -lgtest_main -lgtest \
+ -lstdc++fs \
+ $(PTHREAD_LIBS) \
+ $(OESDK_TESTCASE_FLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(IBM_DBUS_INTERFACES_LIBS) \
+ $(SDBUSPLUS_LIBS)
+
+test_policy_CPPFLAGS = $(test_cppflags)
+test_policy_CXXFLAGS = $(test_cxxflags)
+test_policy_LDFLAGS = $(test_ldflags)
test_policy_SOURCES = test_policy.cpp
test_policy_LDADD = \
$(top_builddir)/policy_table.o \
- $(top_builddir)/policy_find.o \
- -lstdc++fs \
- $(SDBUSPLUS_LIBS)
+ $(top_builddir)/policy_find.o
+
+test_callout_CPPFLAGS = $(test_cppflags)
+test_callout_CXXFLAGS = $(test_cxxflags)
+test_callout_LDFLAGS = $(test_ldflags)
+test_callout_SOURCES = test_callout.cpp
+
+#TODO: remove ObjectPath when it merges in phosphor-dbus-interfaces
+test_callout_LDADD = \
+ $(top_builddir)/xyz/openbmc_project/Common/ObjectPath/server.o \
+ $(top_builddir)/callout.o
+
+
diff --git a/test/test_callout.cpp b/test/test_callout.cpp
new file mode 100644
index 0000000..8d656cc
--- /dev/null
+++ b/test/test_callout.cpp
@@ -0,0 +1,92 @@
+/**
+ * Copyright © 2018 IBM 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.
+ */
+#include <fstream>
+#include <gtest/gtest.h>
+#include <experimental/filesystem>
+#include "callout.hpp"
+#include "dbus.hpp"
+
+using namespace ibm::logging;
+
+class CalloutTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ char dir[] = {"./calloutsXXXXXX"};
+
+ persistDir = mkdtemp(dir);
+ }
+
+ virtual void TearDown()
+ {
+ fs::remove_all(persistDir);
+ }
+
+ fs::path persistDir;
+};
+
+TEST_F(CalloutTest, TestPersist)
+{
+ using namespace std::literals::string_literals;
+
+ auto bus = sdbusplus::bus::new_default();
+ std::string objectPath{"/callout/path/0"};
+ std::string calloutPath{"/some/inventory/object"};
+ size_t id = 0;
+ uint64_t ts = 5;
+
+ DbusPropertyMap assetProps{{"BuildDate"s, Value{"Date42"s}},
+ {"Manufacturer"s, Value{"Mfg42"s}},
+ {"Model"s, Value{"Model42"s}},
+ {"PartNumber"s, Value{"PN42"s}},
+ {"SerialNumber"s, Value{"SN42"s}}};
+ {
+ auto callout = std::make_unique<Callout>(bus, objectPath, calloutPath,
+ id, ts, assetProps);
+ callout->serialize(persistDir);
+
+ ASSERT_EQ(fs::exists(persistDir / std::to_string(id)), true);
+ }
+
+ // Test object restoration
+ {
+ auto callout = std::make_unique<Callout>(bus, objectPath, id, ts);
+
+ ASSERT_EQ(callout->deserialize(persistDir), true);
+
+ ASSERT_EQ(callout->id(), id);
+ ASSERT_EQ(callout->ts(), ts);
+ ASSERT_EQ(callout->path(), calloutPath);
+ ASSERT_EQ(callout->buildDate(),
+ assetProps["BuildDate"].get<std::string>());
+ ASSERT_EQ(callout->manufacturer(),
+ assetProps["Manufacturer"].get<std::string>());
+ ASSERT_EQ(callout->model(), assetProps["Model"].get<std::string>());
+ ASSERT_EQ(callout->partNumber(),
+ assetProps["PartNumber"].get<std::string>());
+ ASSERT_EQ(callout->serialNumber(),
+ assetProps["SerialNumber"].get<std::string>());
+ }
+
+ // Test a serialization failure due to a bad timestamp
+ {
+ auto callout = std::make_unique<Callout>(bus, objectPath, id, ts + 1);
+
+ ASSERT_EQ(callout->deserialize(persistDir), false);
+ ASSERT_EQ(fs::exists(persistDir / std::to_string(id)), false);
+ }
+}