control: Add init trigger support
Add init trigger support to the available triggers where init triggered
event actions are run immediately when fan control starts. Initially the
`getProperties` and `nameHasOwner` methods are supported to be triggered
when fan control starts. The `getProperties` method populates the dbus
object cache for each group member's property configured. The
`nameHasOwner` method populates the service owned state from dbus for
each group member. Each of these attempts to do bulk
reads(getManagedObjects for `getProperties` and getSubTree for
`nameHasOwner`) from dbus when populating the caches to minimize dbus
calls.
Change-Id: Ib5d89eceadd26db1bf7610113ea30fc8ba69ab12
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/Makefile.am b/control/Makefile.am
index 5a70b07..9408358 100644
--- a/control/Makefile.am
+++ b/control/Makefile.am
@@ -32,6 +32,7 @@
json/event.cpp \
json/triggers/timer.cpp \
json/triggers/signal.cpp \
+ json/triggers/init.cpp \
json/actions/default_floor.cpp \
json/actions/request_target_base.cpp \
json/actions/missing_owner_target.cpp \
diff --git a/control/json/triggers/init.cpp b/control/json/triggers/init.cpp
new file mode 100644
index 0000000..160b725
--- /dev/null
+++ b/control/json/triggers/init.cpp
@@ -0,0 +1,165 @@
+/**
+ * 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 "init.hpp"
+
+#include "../manager.hpp"
+#include "action.hpp"
+#include "group.hpp"
+#include "sdbusplus.hpp"
+
+#include <fmt/format.h>
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+namespace phosphor::fan::control::json::trigger::init
+{
+
+using json = nlohmann::json;
+using namespace phosphor::logging;
+
+void getProperties(Manager* mgr, const Group& group)
+{
+ for (const auto& member : group.getMembers())
+ {
+ try
+ {
+ // Check if property already cached
+ auto value = mgr->getProperty(member, group.getInterface(),
+ group.getProperty());
+ if (value == std::nullopt)
+ {
+ // Property not in cache, attempt to add it
+ mgr->addObjects(member, group.getInterface(),
+ group.getProperty());
+ }
+ }
+ catch (const util::DBusMethodError& dme)
+ {
+ // Configured dbus object does not exist on dbus yet?
+ // TODO How to handle this? Create timer to keep checking for
+ // object/service to appear? When to stop checking?
+ }
+ }
+}
+
+void nameHasOwner(Manager* mgr, const Group& group)
+{
+ std::string lastName = "";
+ for (const auto& member : group.getMembers())
+ {
+ std::string servName = "";
+ auto intf = group.getInterface();
+ try
+ {
+ servName = mgr->getService(member, intf);
+ if (!servName.empty() && lastName != servName)
+ {
+ // Member not provided by same service as last group member
+ lastName = servName;
+ auto hasOwner = util::SDBusPlus::callMethodAndRead<bool>(
+ mgr->getBus(), "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus",
+ "NameHasOwner", servName);
+ // Update service name owner state of group object
+ mgr->setOwner(member, servName, intf, hasOwner);
+ }
+ if (servName.empty())
+ {
+ // Path and/or interface configured does not exist on dbus?
+ // TODO How to handle this? Create timer to keep checking for
+ // object/service to appear? When to stop checking?
+ log<level::ERR>(
+ fmt::format(
+ "Unable to get service name for path {}, interface {}",
+ member, intf)
+ .c_str());
+ }
+ }
+ catch (const util::DBusMethodError& dme)
+ {
+ if (!servName.empty())
+ {
+ // Failed to get service name owner state
+ mgr->setOwner(member, servName, intf, false);
+ }
+ else
+ {
+ // Path and/or interface configured does not exist on dbus?
+ // TODO How to handle this? Create timer to keep checking for
+ // object/service to appear? When to stop checking?
+ log<level::ERR>(
+ fmt::format(
+ "Unable to get service name for path {}, interface {}",
+ member, intf)
+ .c_str());
+ throw dme;
+ }
+ }
+ }
+}
+
+void triggerInit(const json& jsonObj, const std::string& eventName,
+ Manager* mgr,
+ std::vector<std::unique_ptr<ActionBase>>& actions)
+{
+ // Get the method handler if configured
+ auto handler = methods.end();
+ if (jsonObj.contains("method"))
+ {
+ auto method = jsonObj["method"].get<std::string>();
+ std::transform(method.begin(), method.end(), method.begin(), tolower);
+ handler = methods.find(method);
+ }
+
+ for (auto& action : actions)
+ {
+ // Groups are optional, so a method is only required if there are groups
+ // i.e.) An init triggered event without any groups results in just
+ // running the actions
+ for (const auto& group : action->getGroups())
+ {
+ if (handler == methods.end())
+ {
+ // Construct list of available methods
+ auto availMethods = std::accumulate(
+ std::next(methods.begin()), methods.end(),
+ methods.begin()->first, [](auto list, auto method) {
+ return std::move(list) + ", " + method.first;
+ });
+ auto msg =
+ fmt::format("Event '{}' requires a supported method given "
+ "to be init driven, available methods: {}",
+ eventName, availMethods);
+ log<level::ERR>(msg.c_str());
+ throw std::runtime_error(msg.c_str());
+ }
+ // Call method handler for each group in the actions
+ handler->second(mgr, group);
+ }
+ // Run the action
+ action->run();
+ }
+}
+
+} // namespace phosphor::fan::control::json::trigger::init
diff --git a/control/json/triggers/init.hpp b/control/json/triggers/init.hpp
new file mode 100644
index 0000000..6447435
--- /dev/null
+++ b/control/json/triggers/init.hpp
@@ -0,0 +1,77 @@
+/**
+ * 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 "group.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace phosphor::fan::control::json::trigger::init
+{
+
+using json = nlohmann::json;
+
+/**
+ * @brief An init method to get properties used in an event
+ *
+ * @param[in] mgr - Pointer to manager of the event
+ * @param[in] group - Group associated with the event
+ */
+void getProperties(Manager* mgr, const Group& group);
+
+/**
+ * @brief An init method to get the owner name of a service used in an event
+ *
+ * @param[in] mgr - Pointer to manager of the event
+ * @param[in] group - Group associated with the event
+ */
+void nameHasOwner(Manager* mgr, const Group& group);
+
+// Handler function for method calls
+using methodHandler = std::function<void(Manager*, const Group&)>;
+
+/* Supported methods to their corresponding handler functions */
+static const std::map<std::string, methodHandler> methods = {
+ {"get_properties", getProperties}, {"name_has_owner", nameHasOwner}};
+
+/**
+ * @brief Trigger to process an event immediately upon fan control starting
+ *
+ * @param[in] jsonObj - JSON object for the trigger
+ * @param[in] eventName - Name of event creating the trigger
+ * @param[in] mgr - Manager to setup the trigger on
+ * @param[in] actions - Actions associated with the trigger
+ *
+ * When fan control starts (or restarts), all events with 'init' triggers are
+ * processed immediately, per its configuration, and its corresponding actions
+ * are run.
+ *
+ * Generally, this type of trigger is paired with a 'signal' class of trigger on
+ * an event so the initial data for an event is collected, processed, and run
+ * before any signal may be received.
+ */
+void triggerInit(const json& jsonObj, const std::string& eventName,
+ Manager* mgr,
+ std::vector<std::unique_ptr<ActionBase>>& actions);
+
+} // namespace phosphor::fan::control::json::trigger::init
diff --git a/control/json/triggers/trigger.hpp b/control/json/triggers/trigger.hpp
index eff36c6..4e7e213 100644
--- a/control/json/triggers/trigger.hpp
+++ b/control/json/triggers/trigger.hpp
@@ -16,6 +16,7 @@
#pragma once
#include "action.hpp"
+#include "init.hpp"
#include "manager.hpp"
#include "signal.hpp"
#include "timer.hpp"
@@ -39,6 +40,8 @@
// Mapping of trigger class name to its creation function
static const std::map<std::string, createTrigger> triggers = {
- {"timer", timer::triggerTimer}, {"signal", signal::triggerSignal}};
+ {"timer", timer::triggerTimer},
+ {"signal", signal::triggerSignal},
+ {"init", init::triggerInit}};
} // namespace phosphor::fan::control::json::trigger