Log device added and device removed into journal
Diff the last configuration file with the current after
some allotted time. If they are different log the change.
Also add ability to check for power status, as some are
only discovered after power change.
Tested: Corrupted backup file so that a device could be
"removed" then discovered.
Change-Id: I610a18b7c66ff183025f2171edddb3c7716a51fb
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/include/EntityManager.hpp b/include/EntityManager.hpp
new file mode 100644
index 0000000..7f46ce2
--- /dev/null
+++ b/include/EntityManager.hpp
@@ -0,0 +1,38 @@
+/*
+// 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 <systemd/sd-journal.h>
+
+#include <iostream>
+#include <string>
+
+inline void logDeviceAdded(const std::string& device)
+{
+
+ sd_journal_send("MESSAGE=%s", "Inventory Added", "PRIORITY=%i", LOG_ERR,
+ "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.InventoryAdded",
+ "REDFISH_MESSAGE_ARGS=%s", device.c_str(), NULL);
+}
+
+inline void logDeviceRemoved(const std::string& device)
+{
+
+ sd_journal_send("MESSAGE=%s", "Inventory Removed", "PRIORITY=%i", LOG_ERR,
+ "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.InventoryRemoved",
+ "REDFISH_MESSAGE_ARGS=%s", device.c_str(), NULL);
+}
\ No newline at end of file
diff --git a/include/Utils.hpp b/include/Utils.hpp
index 990eb14..b4603cb 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -17,15 +17,26 @@
#pragma once
#include "filesystem.hpp"
+#include <boost/container/flat_map.hpp>
+#include <fstream>
+#include <iostream>
#include <nlohmann/json.hpp>
+#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/exception.hpp>
+constexpr const char* configurationOutDir = "/var/configuration/";
+constexpr const char* versionHashFile = "/var/configuration/version";
+constexpr const char* versionFile = "/etc/os-release";
+
bool findFiles(const std::filesystem::path& dirPath,
const std::string& matchString,
std::vector<std::filesystem::path>& foundPaths);
bool validateJson(const nlohmann::json& schemaFile,
const nlohmann::json& input);
+
+bool isPowerOn(void);
+void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn);
struct DBusInternalError final : public sdbusplus::exception_t
{
const char* name() const noexcept override
@@ -42,3 +53,42 @@
"internal error";
};
};
+
+inline bool fwVersionIsSame(void)
+{
+ std::ifstream version(versionFile);
+ if (!version.good())
+ {
+ std::cerr << "Can't read " << versionFile << "\n";
+ return false;
+ }
+
+ std::string versionData;
+ std::string line;
+ while (std::getline(version, line))
+ {
+ versionData += line;
+ }
+
+ std::string expectedHash =
+ std::to_string(std::hash<std::string>{}(versionData));
+
+ std::filesystem::create_directory(configurationOutDir);
+ std::ifstream hashFile(versionHashFile);
+ if (hashFile.good())
+ {
+
+ std::string hashString;
+ hashFile >> hashString;
+
+ if (expectedHash == hashString)
+ {
+ return true;
+ }
+ hashFile.close();
+ }
+
+ std::ofstream output(versionHashFile);
+ output << expectedHash;
+ return false;
+}
\ No newline at end of file
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index c73a69b..a71d553 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -14,6 +14,8 @@
// limitations under the License.
*/
+#include "EntityManager.hpp"
+
#include "filesystem.hpp"
#include <Overlay.hpp>
@@ -33,9 +35,11 @@
#include <sdbusplus/asio/object_server.hpp>
#include <variant>
-constexpr const char* OUTPUT_DIR = "/var/configuration/";
constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
+constexpr const char* tempConfigDir = "/tmp/configuration/";
+constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
+constexpr const char* currentConfiguration = "/var/configuration/system.json";
constexpr const char* globalSchema = "global.json";
constexpr const char* TEMPLATE_CHAR = "$";
constexpr const int32_t MAX_MAPPER_DEPTH = 0;
@@ -98,6 +102,7 @@
// todo: pass this through nicer
std::shared_ptr<sdbusplus::asio::connection> SYSTEM_BUS;
+static nlohmann::json lastJson;
const std::regex ILLEGAL_DBUS_PATH_REGEX("[^A-Za-z0-9_.]");
const std::regex ILLEGAL_DBUS_MEMBER_REGEX("[^A-Za-z0-9_]");
@@ -513,8 +518,8 @@
// writes output files to persist data
bool writeJsonFiles(const nlohmann::json& systemConfiguration)
{
- std::filesystem::create_directory(OUTPUT_DIR);
- std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
+ std::filesystem::create_directory(configurationOutDir);
+ std::ofstream output(currentConfiguration);
if (!output.good())
{
return false;
@@ -1362,6 +1367,14 @@
recordName = probeName;
}
+ auto fromLastJson = lastJson.find(recordName);
+ if (fromLastJson != lastJson.end())
+ {
+ // keep user changes
+ _systemConfiguration[recordName] = *fromLastJson;
+ continue;
+ }
+
// insert into configuration temporarily to be able to
// reference ourselves
@@ -1460,6 +1473,8 @@
}
// overwrite ourselves with cleaned up version
_systemConfiguration[recordName] = record;
+ logDeviceAdded(record.at("Name").get<std::string>());
+
foundDeviceIdx++;
}
});
@@ -1486,8 +1501,76 @@
std::function<void(void)> _callback;
std::vector<std::shared_ptr<PerformProbe>> _probes;
bool _passed = false;
+ bool powerWasOn = isPowerOn();
};
+void startRemovedTimer(boost::asio::deadline_timer& timer,
+ std::vector<sdbusplus::bus::match::match>& dbusMatches,
+ nlohmann::json& systemConfiguration)
+{
+ static bool scannedPowerOff = false;
+ static bool scannedPowerOn = false;
+
+ if (scannedPowerOn)
+ {
+ return;
+ }
+
+ if (!isPowerOn() && scannedPowerOff)
+ {
+ return;
+ }
+
+ timer.expires_from_now(boost::posix_time::seconds(10));
+ timer.async_wait([&systemConfiguration](
+ const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ // we were cancelled
+ return;
+ }
+
+ bool powerOff = !isPowerOn();
+ for (const auto& item : lastJson.items())
+ {
+ if (systemConfiguration.find(item.key()) ==
+ systemConfiguration.end())
+ {
+ bool isDetectedPowerOn = false;
+ auto powerState = item.value().find("PowerState");
+ if (powerState != item.value().end())
+ {
+ auto ptr = powerState->get_ptr<const std::string*>();
+ if (ptr)
+ {
+ if (*ptr == "On" || *ptr == "BiosPost")
+ {
+ isDetectedPowerOn = true;
+ }
+ }
+ }
+ if (powerOff && isDetectedPowerOn)
+ {
+ // power not on yet, don't know if it's there or not
+ continue;
+ }
+ if (!powerOff && scannedPowerOff && isDetectedPowerOn)
+ {
+ // already logged it when power was off
+ continue;
+ }
+
+ logDeviceRemoved(item.value().at("Name").get<std::string>());
+ }
+ }
+ scannedPowerOff = true;
+ if (!powerOff)
+ {
+ scannedPowerOn = true;
+ }
+ });
+}
+
// main properties changed entry
void propertiesChangedCallback(
boost::asio::io_service& io,
@@ -1496,6 +1579,9 @@
sdbusplus::asio::object_server& objServer)
{
static boost::asio::deadline_timer timer(io);
+ static bool timerRunning;
+
+ timerRunning = true;
timer.expires_from_now(boost::posix_time::seconds(1));
// setup an async wait as we normally get flooded with new requests
@@ -1510,6 +1596,7 @@
std::cerr << "async wait error " << ec << "\n";
return;
}
+ timerRunning = false;
nlohmann::json oldConfiguration = systemConfiguration;
DBUS_PROBE_OBJECTS.clear();
@@ -1551,6 +1638,11 @@
io.post([&, newConfiguration]() {
postToDbus(newConfiguration, systemConfiguration,
objServer);
+ if (!timerRunning)
+ {
+ startRemovedTimer(timer, dbusMatches,
+ systemConfiguration);
+ }
});
});
});
@@ -1634,6 +1726,47 @@
});
entityIface->initialize();
+ if (fwVersionIsSame())
+ {
+ if (std::filesystem::is_regular_file(currentConfiguration))
+ {
+ // this file could just be deleted, but it's nice for debug
+ std::filesystem::create_directory(tempConfigDir);
+ std::filesystem::remove(lastConfiguration);
+ std::filesystem::copy(currentConfiguration, lastConfiguration);
+ std::filesystem::remove(currentConfiguration);
+
+ std::ifstream jsonStream(lastConfiguration);
+ if (jsonStream.good())
+ {
+ auto data = nlohmann::json::parse(jsonStream, nullptr, false);
+ if (data.is_discarded())
+ {
+ std::cerr << "syntax error in " << lastConfiguration
+ << "\n";
+ }
+ else
+ {
+ lastJson = std::move(data);
+ }
+ }
+ else
+ {
+ std::cerr << "unable to open " << lastConfiguration << "\n";
+ }
+ }
+ }
+ else
+ {
+ // not an error, just logging at this level to make it in the journal
+ std::cerr << "Clearing previous configuration\n";
+ std::filesystem::remove(currentConfiguration);
+ }
+
+ // some boards only show up after power is on, we want to not say they are
+ // removed until the same state happens
+ setupPowerMatch(SYSTEM_BUS);
+
io.run();
return 0;
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 66841de..160e9fe 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -19,12 +19,15 @@
#include <Utils.hpp>
#include <fstream>
#include <regex>
+#include <sdbusplus/bus/match.hpp>
#include <valijson/adapters/nlohmann_json_adapter.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validator.hpp>
namespace fs = std::filesystem;
+static bool powerStatusOn = false;
+static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
bool findFiles(const fs::path& dirPath, const std::string& matchString,
std::vector<fs::path>& foundPaths)
@@ -59,3 +62,37 @@
}
return true;
}
+
+bool isPowerOn(void)
+{
+ if (!powerMatch)
+ {
+ throw std::runtime_error("Power Match Not Created");
+ }
+ return powerStatusOn;
+}
+
+void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+ // create a match for powergood changes, first time do a method call to
+ // cache the correct value
+ std::function<void(sdbusplus::message::message & message)> eventHandler =
+ [](sdbusplus::message::message& message) {
+ std::string objectName;
+ boost::container::flat_map<std::string, std::variant<int32_t, bool>>
+ values;
+ message.read(objectName, values);
+ auto findPgood = values.find("pgood");
+ if (findPgood != values.end())
+ {
+ powerStatusOn = std::get<int32_t>(findPgood->second);
+ }
+ };
+
+ powerMatch = std::make_unique<sdbusplus::bus::match::match>(
+ static_cast<sdbusplus::bus::bus&>(*conn),
+ "type='signal',interface='org.freedesktop.DBus.Properties',path_"
+ "namespace='/xyz/openbmc_project/Chassis/Control/"
+ "Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
+ eventHandler);
+}
\ No newline at end of file