control: action to override a single fan in a zone - Action class

This change contains the OverrideFanTarget class to create the Action
from a json config. A very basic events.json would look like this:

[
  {
    "name": "test",
    "groups": [
      { "name": "fan0 sensor inventory", "interface": "xyz.openbmc_project.State.Decorator.OperationalStatus",
      "property": { "name": "Functional" } }
    ],
    "triggers": [
      { "class": "init", "method": "get_properties" },
      { "class": "signal", "signal": "properties_changed" }
    ],
    "actions": [ {
      "name": "override_fan_target",
      "count": 1,
      "state": false,
      "fans": [ "fan0", "fan2" ],
      "target": 9999
    } ]
  }
]

When the tach sensor for fan0 is set to non-funtional:

root@p10bmc:~# busctl set-property xyz.openbmc_project.Inventory.Manager
  /xyz/openbmc_project/inventory/system/chassis/motherboard/fan0/fan0_0
  xyz.openbmc_project.State.Decorator.OperationalStatus Functional b false

the run() method for the Action is called and all registered fans will
be locked at the "target" value given in the json "actions" section.

...
<output trimmed>
...
 FAN        TARGET(RPM)  FEEDBACKS(RPMS)   PRESENT   FUNCTIONAL
===============================================================
 fan0              9999       7019/9999      true         true
 fan1             10000       7020/10000     true         true
 fan2              9999       7019/9999      true         true
...

When the inventory registers the sensor back to functional, the override
lock is lifted and the fan is restored to its zone's current target.

Signed-off-by: Mike Capps <mikepcapps@gmail.com>
Change-Id: I6bf5bd55fd6eac2131aec02775a64ad287d3b3b0
diff --git a/control/json/actions/override_fan_target.hpp b/control/json/actions/override_fan_target.hpp
new file mode 100644
index 0000000..6bacf19
--- /dev/null
+++ b/control/json/actions/override_fan_target.hpp
@@ -0,0 +1,171 @@
+/**
+ * Copyright © 2022 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>
+
+namespace phosphor::fan::control::json
+{
+
+using json = nlohmann::json;
+
+/**
+ * @class OverrideFanTarget - Action to override fan targets
+ *
+ * This action locks fans at configured targets when the configured `count`
+ * amount of fans meet criterion for the particular condition. A locked fan
+ * maintains its override target until unlocked (or locked at a higher target).
+ * Upon unlocking, it will either revert to temperature control or activate the
+ * the next-highest target remaining in its list of locks.
+ *
+ * The following config will set all fans in the zone to a target of 9999 if
+ * either fan has a properties_changed event where the Functional property goes
+ * false. The count value of 1 means it only requires one fan, the state value
+ * of false means Functional should go to false to be counted. The signal is
+ * declared under the "triggers" section.
+
+[
+  {
+    "name": "test",
+    "groups": [
+      { "name": "fan0 sensor inventory", "interface":
+"xyz.openbmc_project.State.Decorator.OperationalStatus", "property": { "name":
+"Functional" } }
+    ],
+    "triggers": [
+      { "class": "init", "method": "get_properties" },
+      { "class": "signal", "signal": "properties_changed" }
+    ],
+    "actions": [ {
+      "name": "override_fan_target",
+      "count": 1,
+      "state": false,
+      "fans": [ "fan0", "fan1", "fan2", "fan3" ],
+      "target": 9999
+    } ]
+  }
+]
+ */
+
+class OverrideFanTarget :
+    public ActionBase,
+    public ActionRegister<OverrideFanTarget>
+{
+  public:
+    /* Name of this action */
+    static constexpr auto name = "override_fan_target";
+
+    OverrideFanTarget() = delete;
+    OverrideFanTarget(const OverrideFanTarget&) = delete;
+    OverrideFanTarget(OverrideFanTarget&&) = delete;
+    OverrideFanTarget& operator=(const OverrideFanTarget&) = delete;
+    OverrideFanTarget& operator=(OverrideFanTarget&&) = delete;
+    ~OverrideFanTarget() = default;
+
+    /**
+     * @brief Set target when a number of members are at a given state
+     *
+     * @param[in] jsonObj - JSON configuration of this action
+     * @param[in] groups - Groups of dbus objects the action uses
+     */
+    OverrideFanTarget(const json& jsonObj, const std::vector<Group>& groups);
+
+    /**
+     * @brief Run the action
+     *
+     * Counts the number of members within a group are at or above
+     * the given state. The fans are held at the configured target
+     * until the number of members equal to the given state falls
+     * below the provided count.
+     *
+     * @param[in] zone - Zone to run the action on
+     */
+    void run(Zone& zone) override;
+
+  private:
+    /* action will be triggered when enough group members equal this state*/
+    PropertyVariantType _state;
+
+    /* how many group members required to be at _state to trigger action*/
+    size_t _count;
+
+    /* target for this action */
+    uint64_t _target;
+
+    /* store locked state to know when to unlock */
+    bool _locked = false;
+
+    /* which fans this action applies to */
+    std::vector<std::string> _fans;
+
+    /**
+     * @brief lock all fans in this action
+     *
+     * @param[in] Zone - zone in which _fans are found
+     *
+     */
+    void lockFans(Zone& zone);
+
+    /**
+     * @brief unlock all fans in this action
+     *
+     * @param[in] Zone - zone which _fans are found
+     *
+     */
+    void unlockFans(Zone& zone);
+
+    /**
+     * @brief Parse and set the count
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the number of members that must equal the state
+     */
+    void setCount(const json& jsonObj);
+
+    /**
+     * @brief Parse and set the state
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the state for each member to equal to set the target
+     */
+    void setState(const json& jsonObj);
+
+    /**
+     * @brief Parse and set the target
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the target to use when running the action
+     */
+    void setTarget(const json& jsonObj);
+
+    /**
+     * @brief Parse and set the fans
+     *
+     * @param[in] jsonObj - JSON object for the action
+     *
+     * Sets the fan list the action applies to
+     */
+    void setFans(const json& jsonObj);
+};
+
+} // namespace phosphor::fan::control::json