control: get_managed_objects action

This new action adds all interfaces/properties in its groups into the
Manager's _objects cache, and then calls other actions.

It uses Manager::addObjects() which uses the GetManagedObjects
ObjectManager D-Bus method to obtain all properties provided by the
service, even ones not listed in a group.

This allows an action to run with the latest values in the cache without
having to subscribe to propertiesChanged for them all.

In the below example, the action will call the set_net_increase_target
after it's done populating the cache.

"actions": [
  {
    "name": "get_managed_objects",
    "groups": [
      {
        "name": "the_temps",
        "interface": "xyz.openbmc_project.Sensor.Value",
        "property": {
          "name": "Value"
        }
      }
    ],
    "actions": [
      {
        "name": "set_net_increase_target",
        "state": 30,
        "delta": 100
      }
    ]
  }
]

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ic528392139904abd16181d85138395ccfd68a9a6
diff --git a/control/json/actions/get_managed_objects.cpp b/control/json/actions/get_managed_objects.cpp
new file mode 100644
index 0000000..17914ac
--- /dev/null
+++ b/control/json/actions/get_managed_objects.cpp
@@ -0,0 +1,143 @@
+/**
+ * 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 "get_managed_objects.hpp"
+
+#include "../manager.hpp"
+#include "event.hpp"
+
+#include <iostream>
+
+namespace phosphor::fan::control::json
+{
+
+using json = nlohmann::json;
+
+GetManagedObjects::GetManagedObjects(const json& jsonObj,
+                                     const std::vector<Group>& groups) :
+    ActionBase(jsonObj, groups)
+{
+    setActions(jsonObj);
+}
+
+void GetManagedObjects::run(Zone& zone)
+{
+    std::set<std::string> services;
+
+    // Call Manager::addObjects to refresh the values of the group members.
+    // If there is an ObjectManager interface that handles them, then
+    // the code can combine all members in the same service down to one call.
+    // If no ObjectManager, then still need addObjects calls for each.
+    for (const auto& group : _groups)
+    {
+        for (const auto& member : group.getMembers())
+        {
+            std::vector<std::string> objMgrPaths;
+
+            const auto& service =
+                zone.getManager()->getService(member, group.getInterface());
+
+            if (!service.empty())
+            {
+                objMgrPaths = zone.getManager()->getPaths(
+                    service, "org.freedesktop.DBus.ObjectManager");
+            }
+            else
+            {
+                continue;
+            }
+
+            // Look for the ObjectManager as an ancestor of the path.
+            auto hasObjMgr =
+                std::any_of(objMgrPaths.begin(), objMgrPaths.end(),
+                            [member](const auto& path) {
+                                return member.find(path) != std::string::npos;
+                            });
+
+            if (!hasObjMgr || services.find(service) == services.end())
+            {
+                if (hasObjMgr)
+                {
+                    services.insert(service);
+                }
+
+                zone.getManager()->addObjects(member, group.getInterface(),
+                                              group.getProperty());
+            }
+        }
+    }
+
+    // Perform the actions
+    std::for_each(_actions.begin(), _actions.end(),
+                  [](auto& action) { action->run(); });
+}
+
+void GetManagedObjects::setZones(
+    std::vector<std::reference_wrapper<Zone>>& zones)
+{
+    for (auto& zone : zones)
+    {
+        this->addZone(zone);
+        // Add zone to _actions
+        std::for_each(_actions.begin(), _actions.end(),
+                      [&zone](std::unique_ptr<ActionBase>& action) {
+                          action->addZone(zone);
+                      });
+    }
+}
+
+void GetManagedObjects::setActions(const json& jsonObj)
+{
+    if (!jsonObj.contains("actions"))
+    {
+        return;
+    }
+
+    for (const auto& jsonAct : jsonObj["actions"])
+    {
+        if (!jsonAct.contains("name"))
+        {
+            throw ActionParseError{getName(), "Missing required action name"};
+        }
+
+        // Get any configured profile restrictions on the action
+        std::vector<std::string> profiles;
+        if (jsonAct.contains("profiles"))
+        {
+            profiles = jsonAct["profiles"].get<std::vector<std::string>>();
+        }
+
+        // Set the groups configured for each action run when the timer expires
+        std::vector<Group> groups;
+        Event::setGroups(jsonAct, profiles, groups);
+
+        // If no groups on that action, use our own groups instead
+        const std::vector<Group>* groupPtr = &groups;
+        if (groups.empty())
+        {
+            groupPtr = &_groups;
+        }
+
+        // List of zones is set on these actions by overriden setZones()
+        auto actObj = ActionFactory::getAction(
+            jsonAct["name"].get<std::string>(), jsonAct, *groupPtr, {});
+        if (actObj)
+        {
+            _actions.emplace_back(std::move(actObj));
+        }
+    }
+}
+
+} // namespace phosphor::fan::control::json