bmc/general_systemd: Support systemd status reporting
We should be able to figure out the service status without using a file
since systemd exposes all of this information on DBus
Tested:
Ran on a system and created services that trigger different types of
behavior during verification to simulate running for a couple
seconds and succeeding and failure. Also tested failing as quickly
as possible to ensure the signal capturing is working correctly.
Change-Id: I26358dae7e908a93b710587121fa104dd40ea661
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/bmc/general_systemd.cpp b/bmc/general_systemd.cpp
index 5b9fb87..bcdda0d 100644
--- a/bmc/general_systemd.cpp
+++ b/bmc/general_systemd.cpp
@@ -27,6 +27,132 @@
namespace ipmi_flash
{
+static constexpr auto systemdService = "org.freedesktop.systemd1";
+static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
+static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
+
+bool SystemdNoFile::trigger()
+{
+ if (job)
+ {
+ std::fprintf(stderr, "Job alreading running %s: %s\n",
+ triggerService.c_str(), job->c_str());
+ return false;
+ }
+
+ try
+ {
+ jobMonitor.emplace(
+ bus,
+ "type='signal',"
+ "sender='org.freedesktop.systemd1',"
+ "path='/org/freedesktop/systemd1',"
+ "interface='org.freedesktop.systemd1.Manager',"
+ "member='JobRemoved',",
+ [&](sdbusplus::message::message& m) { this->match(m); });
+
+ auto method = bus.new_method_call(systemdService, systemdRoot,
+ systemdInterface, "StartUnit");
+ method.append(triggerService);
+ method.append(mode);
+
+ sdbusplus::message::object_path obj_path;
+ bus.call(method).read(obj_path);
+ job = std::move(obj_path);
+ std::fprintf(stderr, "Triggered %s mode %s: %s\n",
+ triggerService.c_str(), mode.c_str(), job->c_str());
+ currentStatus = ActionStatus::running;
+ return true;
+ }
+ catch (const std::exception& e)
+ {
+ job = std::nullopt;
+ jobMonitor = std::nullopt;
+ currentStatus = ActionStatus::failed;
+ std::fprintf(stderr, "Failed to trigger %s mode %s: %s\n",
+ triggerService.c_str(), mode.c_str(), e.what());
+ return false;
+ }
+}
+
+void SystemdNoFile::abort()
+{
+ if (!job)
+ {
+ std::fprintf(stderr, "No running job %s\n", triggerService.c_str());
+ return;
+ }
+
+ // Cancel the job
+ auto cancel_req = bus.new_method_call(systemdService, job->c_str(),
+ systemdInterface, "Cancel");
+ try
+ {
+ bus.call_noreply(cancel_req);
+ std::fprintf(stderr, "Canceled %s: %s\n", triggerService.c_str(),
+ job->c_str());
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ std::fprintf(stderr, "Failed to cancel job %s %s: %s\n",
+ triggerService.c_str(), job->c_str(), ex.what());
+ }
+}
+
+ActionStatus SystemdNoFile::status()
+{
+ return currentStatus;
+}
+
+const std::string& SystemdNoFile::getMode() const
+{
+ return mode;
+}
+
+void SystemdNoFile::match(sdbusplus::message::message& m)
+{
+ if (!job)
+ {
+ std::fprintf(stderr, "No running job %s\n", triggerService.c_str());
+ return;
+ }
+
+ uint32_t job_id;
+ sdbusplus::message::object_path job_path;
+ std::string unit;
+ std::string result;
+ try
+ {
+ m.read(job_id, job_path, unit, result);
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ std::fprintf(stderr, "Bad JobRemoved signal %s: %s\n",
+ triggerService.c_str(), e.what());
+ return;
+ }
+
+ if (*job != job_path.str)
+ {
+ return;
+ }
+
+ std::fprintf(stderr, "Job Finished %s %s: %s\n", triggerService.c_str(),
+ job->c_str(), result.c_str());
+ jobMonitor = std::nullopt;
+ job = std::nullopt;
+ currentStatus =
+ result == "done" ? ActionStatus::success : ActionStatus::failed;
+}
+
+std::unique_ptr<TriggerableActionInterface>
+ SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::bus&& bus,
+ const std::string& service,
+ const std::string& mode)
+{
+ return std::make_unique<SystemdNoFile>(std::move(bus), service, mode);
+}
+
std::unique_ptr<TriggerableActionInterface>
SystemdWithStatusFile::CreateSystemdWithStatusFile(
sdbusplus::bus::bus&& bus, const std::string& path,
@@ -36,40 +162,12 @@
service, mode);
}
-bool SystemdWithStatusFile::trigger()
-{
- static constexpr auto systemdService = "org.freedesktop.systemd1";
- static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
- static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
-
- auto method = bus.new_method_call(systemdService, systemdRoot,
- systemdInterface, "StartUnit");
- method.append(triggerService);
- method.append(mode);
-
- try
- {
- bus.call_noreply(method);
- }
- catch (const sdbusplus::exception::SdBusError& ex)
- {
- /* TODO: Once logging supports unit-tests, add a log message to test
- * this failure.
- */
- return false;
- }
-
- return true;
-}
-
-void SystemdWithStatusFile::abort()
-{
- /* TODO: Implement this. */
-}
-
ActionStatus SystemdWithStatusFile::status()
{
- ActionStatus result = ActionStatus::unknown;
+ // Assume a status based on job execution if there is no file
+ ActionStatus result = SystemdNoFile::status() == ActionStatus::running
+ ? ActionStatus::running
+ : ActionStatus::failed;
std::ifstream ifs;
ifs.open(checkPath);
@@ -98,59 +196,4 @@
return result;
}
-const std::string SystemdWithStatusFile::getMode() const
-{
- return mode;
-}
-
-std::unique_ptr<TriggerableActionInterface>
- SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::bus&& bus,
- const std::string& service,
- const std::string& mode)
-{
- return std::make_unique<SystemdNoFile>(std::move(bus), service, mode);
-}
-
-bool SystemdNoFile::trigger()
-{
- static constexpr auto systemdService = "org.freedesktop.systemd1";
- static constexpr auto systemdRoot = "/org/freedesktop/systemd1";
- static constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
-
- auto method = bus.new_method_call(systemdService, systemdRoot,
- systemdInterface, "StartUnit");
- method.append(triggerService);
- method.append(mode);
-
- try
- {
- bus.call_noreply(method);
- state = ActionStatus::running;
- return true;
- }
- catch (const sdbusplus::exception::SdBusError& ex)
- {
- /* TODO: Once logging supports unit-tests, add a log message to test
- * this failure.
- */
- state = ActionStatus::failed;
- return false;
- }
-}
-
-void SystemdNoFile::abort()
-{
- return;
-}
-
-ActionStatus SystemdNoFile::status()
-{
- return state;
-}
-
-const std::string SystemdNoFile::getMode() const
-{
- return mode;
-}
-
} // namespace ipmi_flash
diff --git a/bmc/general_systemd.hpp b/bmc/general_systemd.hpp
index c7fe4cb..a8d9a88 100644
--- a/bmc/general_systemd.hpp
+++ b/bmc/general_systemd.hpp
@@ -4,16 +4,56 @@
#include <memory>
#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
#include <string>
namespace ipmi_flash
{
+class SystemdNoFile : public TriggerableActionInterface
+{
+ public:
+ static std::unique_ptr<TriggerableActionInterface>
+ CreateSystemdNoFile(sdbusplus::bus::bus&& bus,
+ const std::string& service,
+ const std::string& mode);
+
+ SystemdNoFile(sdbusplus::bus::bus&& bus, const std::string& service,
+ const std::string& mode) :
+ bus(std::move(bus)),
+ triggerService(service), mode(mode)
+ {
+ }
+
+ SystemdNoFile(const SystemdNoFile&) = delete;
+ SystemdNoFile& operator=(const SystemdNoFile&) = delete;
+ // sdbusplus match requires us to be pinned
+ SystemdNoFile(SystemdNoFile&&) = delete;
+ SystemdNoFile& operator=(SystemdNoFile&&) = delete;
+
+ bool trigger() override;
+ void abort() override;
+ ActionStatus status() override;
+
+ const std::string& getMode() const;
+
+ private:
+ sdbusplus::bus::bus bus;
+ const std::string triggerService;
+ const std::string mode;
+
+ std::optional<sdbusplus::bus::match::match> jobMonitor;
+ std::optional<std::string> job;
+ ActionStatus currentStatus = ActionStatus::unknown;
+
+ void match(sdbusplus::message::message& m);
+};
+
/**
* Representation of what is used for triggering an action with systemd and
* checking the result by reading a file.
*/
-class SystemdWithStatusFile : public TriggerableActionInterface
+class SystemdWithStatusFile : public SystemdNoFile
{
public:
/**
@@ -34,62 +74,15 @@
SystemdWithStatusFile(sdbusplus::bus::bus&& bus, const std::string& path,
const std::string& service, const std::string& mode) :
- bus(std::move(bus)),
- checkPath(path), triggerService(service), mode(mode)
+ SystemdNoFile(std::move(bus), service, mode),
+ checkPath(path)
{
}
- ~SystemdWithStatusFile() = default;
- SystemdWithStatusFile(const SystemdWithStatusFile&) = delete;
- SystemdWithStatusFile& operator=(const SystemdWithStatusFile&) = delete;
- SystemdWithStatusFile(SystemdWithStatusFile&&) = default;
- SystemdWithStatusFile& operator=(SystemdWithStatusFile&&) = default;
-
- bool trigger() override;
- void abort() override;
ActionStatus status() override;
- const std::string getMode() const;
-
private:
- sdbusplus::bus::bus bus;
const std::string checkPath;
- const std::string triggerService;
- const std::string mode;
-};
-
-class SystemdNoFile : public TriggerableActionInterface
-{
- public:
- static std::unique_ptr<TriggerableActionInterface>
- CreateSystemdNoFile(sdbusplus::bus::bus&& bus,
- const std::string& service,
- const std::string& mode);
-
- SystemdNoFile(sdbusplus::bus::bus&& bus, const std::string& service,
- const std::string& mode) :
- bus(std::move(bus)),
- triggerService(service), mode(mode)
- {
- }
-
- ~SystemdNoFile() = default;
- SystemdNoFile(const SystemdNoFile&) = delete;
- SystemdNoFile& operator=(const SystemdNoFile&) = delete;
- SystemdNoFile(SystemdNoFile&&) = default;
- SystemdNoFile& operator=(SystemdNoFile&&) = default;
-
- bool trigger() override;
- void abort() override;
- ActionStatus status() override;
-
- const std::string getMode() const;
-
- private:
- sdbusplus::bus::bus bus;
- const std::string triggerService;
- const std::string mode;
- ActionStatus state = ActionStatus::unknown;
};
} // namespace ipmi_flash