control: Add net target increase action

Add the YAML based set_net_speed_increase action function as an action
class for JSON configs to use.

Change-Id: Ia7e5cb8bcdb4bba0ebf356d9dc774ab292f0e8fc
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/Makefile.am b/control/Makefile.am
index d90adc1..d6fa5a5 100644
--- a/control/Makefile.am
+++ b/control/Makefile.am
@@ -34,7 +34,8 @@
 	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/count_state_target.cpp \
+	json/actions/net_target_increase.cpp
 else
 phosphor_fan_control_SOURCES += \
 	argument.cpp \
diff --git a/control/json/actions/net_target_increase.cpp b/control/json/actions/net_target_increase.cpp
new file mode 100644
index 0000000..ccfd897
--- /dev/null
+++ b/control/json/actions/net_target_increase.cpp
@@ -0,0 +1,142 @@
+/**
+ * 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 "net_target_increase.hpp"
+
+#include "../manager.hpp"
+#include "../zone.hpp"
+#include "action.hpp"
+#include "group.hpp"
+
+#include <fmt/format.h>
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <algorithm>
+#include <variant>
+
+namespace phosphor::fan::control::json
+{
+
+using json = nlohmann::json;
+using namespace phosphor::logging;
+
+NetTargetIncrease::NetTargetIncrease(const json& jsonObj) : ActionBase(jsonObj)
+{
+    setState(jsonObj);
+    setDelta(jsonObj);
+}
+
+void NetTargetIncrease::run(Zone& zone, const Group& group)
+{
+    auto netDelta = zone.getIncDelta();
+    const auto& members = group.getMembers();
+    std::for_each(
+        members.begin(), members.end(),
+        [this, &zone, &group, &netDelta](const auto& member) {
+            try
+            {
+                auto value = Manager::getObjValueVariant(
+                    member, group.getInterface(), group.getProperty());
+                if (std::holds_alternative<int64_t>(value) ||
+                    std::holds_alternative<double>(value))
+                {
+                    // Where a group of int/doubles are greater than or equal
+                    // to the state(some value) provided, request an increase
+                    // of the configured delta times the difference between
+                    // the group member's value and configured state value.
+                    if (value >= _state)
+                    {
+                        uint64_t incDelta = 0;
+                        if (auto dblPtr = std::get_if<double>(&value))
+                        {
+                            incDelta = static_cast<uint64_t>(
+                                (*dblPtr - std::get<double>(_state)) * _delta);
+                        }
+                        else
+                        {
+                            // Increase by at least a single delta
+                            // to attempt bringing under provided 'state'
+                            auto deltaFactor =
+                                std::max((std::get<int64_t>(value) -
+                                          std::get<int64_t>(_state)),
+                                         1ll);
+                            incDelta =
+                                static_cast<uint64_t>(deltaFactor * _delta);
+                        }
+                        netDelta = std::max(netDelta, incDelta);
+                    }
+                }
+                else if (std::holds_alternative<bool>(value))
+                {
+                    // Where a group of booleans equal the state(`true` or
+                    // `false`) provided, request an increase of the configured
+                    // delta
+                    if (_state == value)
+                    {
+                        netDelta = std::max(netDelta, _delta);
+                    }
+                }
+                else if (std::holds_alternative<std::string>(value))
+                {
+                    // Where a group of strings equal the state(some string)
+                    // provided, request an increase of the configured delta
+                    if (_state == value)
+                    {
+                        netDelta = std::max(netDelta, _delta);
+                    }
+                }
+                else
+                {
+                    // Unsupported group member type for this action
+                    log<level::ERR>(
+                        fmt::format("Action {}: Unsupported group member type "
+                                    "given. [object = {} : {} : {}]",
+                                    ActionBase::getName(), member,
+                                    group.getInterface(), group.getProperty())
+                            .c_str());
+                }
+            }
+            catch (const std::out_of_range& oore)
+            {
+                // Property value not found, netDelta unchanged
+            }
+        });
+    // Request increase to target
+    zone.requestIncrease(netDelta);
+}
+
+void NetTargetIncrease::setState(const json& jsonObj)
+{
+    if (!jsonObj.contains("state"))
+    {
+        throw ActionParseError{ActionBase::getName(),
+                               "Missing required state value"};
+    }
+    _state = getJsonValue(jsonObj["state"]);
+}
+
+void NetTargetIncrease::setDelta(const json& jsonObj)
+{
+    if (!jsonObj.contains("delta"))
+    {
+        throw ActionParseError{ActionBase::getName(),
+                               "Missing required delta value"};
+    }
+    _delta = jsonObj["delta"].get<uint64_t>();
+}
+
+} // namespace phosphor::fan::control::json
diff --git a/control/json/actions/net_target_increase.hpp b/control/json/actions/net_target_increase.hpp
new file mode 100644
index 0000000..8c6a043
--- /dev/null
+++ b/control/json/actions/net_target_increase.hpp
@@ -0,0 +1,108 @@
+/**
+ * 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 "../zone.hpp"
+#include "action.hpp"
+#include "group.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <optional>
+
+namespace phosphor::fan::control::json
+{
+
+using json = nlohmann::json;
+
+/**
+ * @class NetTargetIncrease - Action to determine the net target increase to
+ * request
+ *
+ * Calculates the net target increase to be requested based on the value of each
+ * property given within a group. The net target increase is the maximum delta
+ * determined from all of the properties of the group. This net target increase
+ * is the increase change that's requested to the current target of a zone.
+ */
+class NetTargetIncrease :
+    public ActionBase,
+    public ActionRegister<NetTargetIncrease>
+{
+  public:
+    /* Name of this action */
+    static constexpr auto name = "set_net_increase_speed";
+
+    NetTargetIncrease() = delete;
+    NetTargetIncrease(const NetTargetIncrease&) = delete;
+    NetTargetIncrease(NetTargetIncrease&&) = delete;
+    NetTargetIncrease& operator=(const NetTargetIncrease&) = delete;
+    NetTargetIncrease& operator=(NetTargetIncrease&&) = delete;
+    ~NetTargetIncrease() = default;
+
+    /**
+     * @brief Determine/Set the net target increase
+     *
+     * @param[in] jsonObj - JSON configuration of this action
+     */
+    explicit NetTargetIncrease(const json& jsonObj);
+
+    /**
+     * @brief Run the action
+     *
+     * Determines the net target increase delta to be requested based on the
+     * property values of the group of dbus objects and requests this target
+     * increase on the zone. The property values of the group is compared to
+     * the configured state value to determine if an increase delta should be
+     * requested. For boolean & string values, the configured delta is
+     * requested when a property of the group equals the configured state. For
+     * integer & double values, the configured delta is multiplied by the
+     * difference in property value and the configured state resulting in a net
+     * target increase for each group member. After all members of the group of
+     * dbus objects are processed, the maximum net target increase calculated is
+     * requested on the zone.
+     *
+     * @param[in] zone - Zone to run the action on
+     * @param[in] group - Group of dbus objects the action runs against
+     */
+    void run(Zone& zone, const Group& group) override;
+
+  private:
+    /* State the members must be at to increase the rarget */
+    PropertyVariantType _state;
+
+    /* Increase delta for this action */
+    uint64_t _delta;
+
+    /**
+     * @brief Parse and set the state
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the state to compare members to
+     */
+    void setState(const json& jsonObj);
+
+    /**
+     * @brief Parse and set the delta
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the increase delta to use when running the action
+     */
+    void setDelta(const json& jsonObj);
+};
+
+} // namespace phosphor::fan::control::json
diff --git a/control/json/zone.hpp b/control/json/zone.hpp
index 8bf132c..2f6e925 100644
--- a/control/json/zone.hpp
+++ b/control/json/zone.hpp
@@ -136,6 +136,16 @@
     }
 
     /**
+     * @brief Get the target increase delta
+     *
+     * @return - The current target increase delta
+     */
+    inline auto& getIncDelta() const
+    {
+        return _incDelta;
+    };
+
+    /**
      * @brief Add a fan object to the zone
      *
      * @param[in] fan - Unique pointer to a fan object that will be moved into