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/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