control: Add signal triggers with propertiesChanged signals
Add signal trigger support to the available triggers and include the
ability to subscribe and handle propertiesChanged signals.
Subscribing to a signal involves creating the signal's match string and
packaging the signal data for when the signal is received. Since a
single signal could be configured to be used across multiple dbus
objects and/or actions, the signal package is added in a way that each
event configured for the signal is processed from the signal received.
Handling the propertiesChanged signal involves filtering for the
configured dbus property, updating the cached value of the property, and
then allowing the actions for that signal to be run.
Change-Id: I04bc163b65115d9bac30315f690db5fefca5bde4
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/Makefile.am b/control/Makefile.am
index 8f0a11e..5a70b07 100644
--- a/control/Makefile.am
+++ b/control/Makefile.am
@@ -30,13 +30,14 @@
json/zone.cpp \
json/group.cpp \
json/event.cpp \
+ json/triggers/timer.cpp \
+ json/triggers/signal.cpp \
json/actions/default_floor.cpp \
json/actions/request_target_base.cpp \
json/actions/missing_owner_target.cpp \
json/actions/count_state_target.cpp \
json/actions/net_target_increase.cpp \
- json/actions/net_target_decrease.cpp \
- json/triggers/timer.cpp
+ json/actions/net_target_decrease.cpp
else
phosphor_fan_control_SOURCES += \
argument.cpp \
diff --git a/control/json/manager.hpp b/control/json/manager.hpp
index 7cc72a9..294ba7d 100644
--- a/control/json/manager.hpp
+++ b/control/json/manager.hpp
@@ -260,6 +260,20 @@
const std::string& intf);
/**
+ * @brief Set/update an object's property value
+ *
+ * @param[in] path - Dbus object's path
+ * @param[in] intf - Dbus object's interface
+ * @param[in] prop - Dbus object's property
+ * @param[in] value - Dbus object's property value
+ */
+ void setProperty(const std::string& path, const std::string& intf,
+ const std::string& prop, PropertyVariantType value)
+ {
+ _objects[path][intf][prop] = std::move(value);
+ }
+
+ /**
* @brief Get the object's property value as a variant
*
* @param[in] path - Path of the object containing the property
diff --git a/control/json/triggers/handlers.hpp b/control/json/triggers/handlers.hpp
new file mode 100644
index 0000000..2a52757
--- /dev/null
+++ b/control/json/triggers/handlers.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "../manager.hpp"
+
+#include <sdbusplus/message.hpp>
+
+#include <algorithm>
+#include <map>
+#include <tuple>
+#include <vector>
+
+namespace phosphor::fan::control::json::trigger::signal
+{
+
+using namespace sdbusplus::message;
+
+struct Handlers
+{
+
+ public:
+ /**
+ * @brief Processes a properties changed signal and updates the property's
+ * value in the manager's object cache
+ *
+ * @param[in] msg - The sdbusplus signal message
+ * @param[in] obj - Object data associated with the signal
+ * @param[in] mgr - Manager that stores the object cache
+ */
+ static bool propertiesChanged(message& msg, const SignalObject& obj,
+ Manager& mgr)
+ {
+ std::string intf;
+ msg.read(intf);
+ if (intf != std::get<Intf>(obj))
+ {
+ // Interface name does not match object's interface
+ return false;
+ }
+
+ std::map<std::string, PropertyVariantType> props;
+ msg.read(props);
+ auto itProp = props.find(std::get<Prop>(obj));
+ if (itProp == props.cend())
+ {
+ // Object's property not in dictionary of properties changed
+ return false;
+ }
+
+ mgr.setProperty(std::get<Path>(obj), std::get<Intf>(obj),
+ std::get<Prop>(obj), itProp->second);
+ return true;
+ }
+};
+
+} // namespace phosphor::fan::control::json::trigger::signal
diff --git a/control/json/triggers/signal.cpp b/control/json/triggers/signal.cpp
new file mode 100644
index 0000000..b5e1e6a
--- /dev/null
+++ b/control/json/triggers/signal.cpp
@@ -0,0 +1,154 @@
+/**
+ * Copyright © 2021 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 "signal.hpp"
+
+#include "../manager.hpp"
+#include "action.hpp"
+#include "group.hpp"
+#include "handlers.hpp"
+
+#include <fmt/format.h>
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+namespace phosphor::fan::control::json::trigger::signal
+{
+
+using json = nlohmann::json;
+using namespace phosphor::logging;
+using namespace sdbusplus::bus::match;
+
+void subscribe(const std::string& match, SignalPkg&& signalPkg,
+ std::function<bool(SignalPkg&)> isSameSig, Manager* mgr)
+{
+ // TODO - Handle signal subscriptions to objects hosted by fan control
+ auto& signalData = mgr->getSignal(match);
+ if (signalData.empty())
+ {
+ // Signal subscription doesnt exist, add signal package and subscribe
+ std::vector<SignalPkg> pkgs = {signalPkg};
+ std::unique_ptr<std::vector<SignalPkg>> dataPkgs =
+ std::make_unique<std::vector<SignalPkg>>(std::move(pkgs));
+ std::unique_ptr<sdbusplus::server::match::match> ptrMatch = nullptr;
+ if (!match.empty())
+ {
+ // Subscribe to signal
+ ptrMatch = std::make_unique<sdbusplus::server::match::match>(
+ mgr->getBus(), match.c_str(),
+ std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr),
+ std::placeholders::_1, dataPkgs.get()));
+ }
+ signalData.emplace_back(std::move(dataPkgs), std::move(ptrMatch));
+ }
+ else
+ {
+ // Signal subscription already exists
+ // Only a single signal data entry tied to each match is supported
+ auto& pkgs = std::get<std::unique_ptr<std::vector<SignalPkg>>>(
+ signalData.front());
+ for (auto& pkg : *pkgs)
+ {
+ if (isSameSig(pkg))
+ {
+ // Same signal expected, add action to be run when signal
+ // received
+ auto& pkgActions = std::get<SignalActions>(signalPkg);
+ auto& actions = std::get<SignalActions>(pkg);
+ // Only one action is given on the signal package passed in
+ actions.push_back(pkgActions.front());
+ }
+ else
+ {
+ // Expected signal differs, add signal package
+ (*pkgs).emplace_back(std::move(signalPkg));
+ }
+ }
+ }
+}
+
+void propertiesChanged(Manager* mgr, const std::string& eventName,
+ std::unique_ptr<ActionBase>& action)
+{
+ // Groups are optional, but a signal triggered event with no groups
+ // will do nothing since signals require a group
+ for (const auto& group : action->getGroups())
+ {
+ for (const auto& member : group.getMembers())
+ {
+ // Setup property changed signal handler on the group member's
+ // property
+ const auto match =
+ rules::propertiesChanged(member, group.getInterface());
+ SignalPkg signalPkg = {Handlers::propertiesChanged,
+ SignalObject(std::cref(member),
+ std::cref(group.getInterface()),
+ std::cref(group.getProperty())),
+ SignalActions({action})};
+ auto isSameSig = [&prop = group.getProperty()](SignalPkg& pkg) {
+ auto& obj = std::get<SignalObject>(pkg);
+ return prop == std::get<Prop>(obj);
+ };
+
+ subscribe(match, std::move(signalPkg), isSameSig, mgr);
+ }
+ }
+}
+
+void triggerSignal(const json& jsonObj, const std::string& eventName,
+ Manager* mgr,
+ std::vector<std::unique_ptr<ActionBase>>& actions)
+{
+ auto subscriber = signals.end();
+ if (jsonObj.contains("signal"))
+ {
+ auto signal = jsonObj["signal"].get<std::string>();
+ std::transform(signal.begin(), signal.end(), signal.begin(), tolower);
+ subscriber = signals.find(signal);
+ }
+ if (subscriber == signals.end())
+ {
+ // Construct list of available signals
+ auto availSignals =
+ std::accumulate(std::next(signals.begin()), signals.end(),
+ signals.begin()->first, [](auto list, auto signal) {
+ return std::move(list) + ", " + signal.first;
+ });
+ auto msg =
+ fmt::format("Event '{}' requires a supported signal given to be "
+ "triggered by signal, available signals: {}",
+ eventName, availSignals);
+ log<level::ERR>(msg.c_str());
+ throw std::runtime_error(msg.c_str());
+ }
+
+ for (auto& action : actions)
+ {
+ // Call signal subscriber for each group in the action
+ subscriber->second(mgr, eventName, action);
+ }
+}
+
+} // namespace phosphor::fan::control::json::trigger::signal
diff --git a/control/json/triggers/signal.hpp b/control/json/triggers/signal.hpp
new file mode 100644
index 0000000..1077144
--- /dev/null
+++ b/control/json/triggers/signal.hpp
@@ -0,0 +1,78 @@
+/**
+ * Copyright © 2021 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.
+ */
+#pragma once
+
+#include "../manager.hpp"
+#include "action.hpp"
+
+#include <nlohmann/json.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <functional>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+namespace phosphor::fan::control::json::trigger::signal
+{
+
+using json = nlohmann::json;
+
+/**
+ * @brief Subscribe to a signal
+ *
+ * @param[in] match - Signal match string to subscribe to
+ * @param[in] pkg - Data package to attach to signal
+ * @param[in] mgr - Pointer to manager of the trigger
+ */
+void subscribe(const std::string& match, SignalPkg&& pkg,
+ std::function<bool(SignalPkg&)> isSameSig, Manager* mgr);
+
+/**
+ * @brief Subscribes to a propertiesChanged signal
+ *
+ * @param[in] mgr - Pointer to manager of the trigger
+ * @param[in] eventName - Name of event associated to the signal
+ * @param[in] action - Action to be run when signal is received
+ */
+void propertiesChanged(Manager* mgr, const std::string& eventName,
+ std::unique_ptr<ActionBase>& action);
+
+// Match setup function for signals
+using SignalMatch = std::function<void(Manager*, const std::string&,
+ std::unique_ptr<ActionBase>& action)>;
+
+/* Supported signals to their corresponding match setup functions */
+static const std::unordered_map<std::string, SignalMatch> signals = {
+ {"properties_changed", propertiesChanged}};
+
+/**
+ * @brief Trigger to process an event after a signal is received
+ *
+ * @param[in] jsonObj - JSON object for the trigger
+ * @param[in] eventName - Name of event associated to the signal
+ * @param[in] mgr - Pointer to manager of the trigger
+ * @param[in] actions - Actions associated with the trigger
+ *
+ * When fan control starts (or restarts), all events with 'signal' triggers are
+ * subscribed to run its corresponding actions when a signal, per its
+ * configuration, is received.
+ */
+void triggerSignal(const json& jsonObj, const std::string& eventName,
+ Manager* mgr,
+ std::vector<std::unique_ptr<ActionBase>>& actions);
+
+} // namespace phosphor::fan::control::json::trigger::signal
diff --git a/control/json/triggers/trigger.hpp b/control/json/triggers/trigger.hpp
index b5fb902..eff36c6 100644
--- a/control/json/triggers/trigger.hpp
+++ b/control/json/triggers/trigger.hpp
@@ -17,6 +17,7 @@
#include "action.hpp"
#include "manager.hpp"
+#include "signal.hpp"
#include "timer.hpp"
#include <nlohmann/json.hpp>
@@ -38,6 +39,6 @@
// Mapping of trigger class name to its creation function
static const std::map<std::string, createTrigger> triggers = {
- {"timer", timer::triggerTimer}};
+ {"timer", timer::triggerTimer}, {"signal", signal::triggerSignal}};
} // namespace phosphor::fan::control::json::trigger