control: mapped_floor: Add conditions

Add the concept of a condition to the mapped_floor action such that it
will only run if the condition is met.  If the condition isn't met, the
run() function will just exit immediately.

A condition is created by placing the following in the JSON:

* condition_group
  - The name of the group that has the property the condition
    will use.  For now, it must be a single member group.
* condition_value
  - The value the property has to be to meet the condition.
* condition_op
  - Either 'equal' or 'not_equal', where the property has to
    either be equal to or not equal to the condition value.

For example, the following says the single member of the 'cpu 0' group
must have its Model property be equal to "1234" for the action to run:

{
    "groups": [{
        "name": "cpu 0",
        "interface": "xyz.openbmc_project.Inventory.Decorator.Asset",
        "property": { "name": "Model" }
      }
      ...
    ],
    ...
    "name": "mapped_floor",
    "key_group": "ambient temp",
    "condition_group": "cpu 0",
    "condition_value": "1234",
    "condition_op": "equal",
    ...
}

If a condition is present but isn't met, the action will remove its
floor hold if it has one to support the case of the condition property
changing values.

Change-Id: I3ede20efd334e2c5292a441c089534420959c7bc
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/control/json/actions/mapped_floor.cpp b/control/json/actions/mapped_floor.cpp
index 55414ac..906559b 100644
--- a/control/json/actions/mapped_floor.cpp
+++ b/control/json/actions/mapped_floor.cpp
@@ -59,6 +59,7 @@
     setKeyGroup(jsonObj);
     setFloorTable(jsonObj);
     setDefaultFloor(jsonObj);
+    setCondition(jsonObj);
 }
 
 const Group* MappedFloor::getGroup(const std::string& name)
@@ -178,6 +179,53 @@
     }
 }
 
+void MappedFloor::setCondition(const json& jsonObj)
+{
+    // condition_group, condition_value, and condition_op
+    // are optional, though they must show up together.
+    // Assume if condition_group is present then they all
+    // must be.
+    if (!jsonObj.contains("condition_group"))
+    {
+        return;
+    }
+
+    _conditionGroup = getGroup(jsonObj["condition_group"].get<std::string>());
+
+    if (_conditionGroup->getMembers().size() != 1)
+    {
+        throw ActionParseError{
+            ActionBase::getName(),
+            fmt::format("condition_group {} must only have 1 member",
+                        _conditionGroup->getName())};
+    }
+
+    if (!jsonObj.contains("condition_value"))
+    {
+        throw ActionParseError{ActionBase::getName(),
+                               "Missing required 'condition_value' entry in "
+                               "mapped_floor action"};
+    }
+
+    _conditionValue = getJsonValue(jsonObj["condition_value"]);
+
+    if (!jsonObj.contains("condition_op"))
+    {
+        throw ActionParseError{ActionBase::getName(),
+                               "Missing required 'condition_op' entry in "
+                               "mapped_floor action"};
+    }
+
+    _conditionOp = jsonObj["condition_op"].get<std::string>();
+
+    if ((_conditionOp != "equal") && (_conditionOp != "not_equal"))
+    {
+        throw ActionParseError{ActionBase::getName(),
+                               "Invalid 'condition_op' value in "
+                               "mapped_floor action"};
+    }
+}
+
 /**
  * @brief Converts the variant to a double if it's a
  *        int32_t or int64_t.
@@ -254,8 +302,65 @@
     return max;
 }
 
+bool MappedFloor::meetsCondition()
+{
+    if (!_conditionGroup)
+    {
+        return true;
+    }
+
+    bool meets = false;
+
+    // setCondition() also checks these
+    assert(_conditionGroup->getMembers().size() == 1);
+    assert((_conditionOp == "equal") || (_conditionOp == "not_equal"));
+
+    const auto& member = _conditionGroup->getMembers()[0];
+
+    try
+    {
+        auto value =
+            Manager::getObjValueVariant(member, _conditionGroup->getInterface(),
+                                        _conditionGroup->getProperty());
+
+        if ((_conditionOp == "equal") && (value == _conditionValue))
+        {
+            meets = true;
+        }
+        else if ((_conditionOp == "not_equal") && (value != _conditionValue))
+        {
+            meets = true;
+        }
+    }
+    catch (const std::out_of_range& e)
+    {
+        // Property not there, so consider it failing the 'equal'
+        // condition and passing the 'not_equal' condition.
+        if (_conditionOp == "equal")
+        {
+            meets = false;
+        }
+        else // not_equal
+        {
+            meets = true;
+        }
+    }
+
+    return meets;
+}
+
 void MappedFloor::run(Zone& zone)
 {
+    if (!meetsCondition())
+    {
+        // Make sure this no longer has a floor hold
+        if (zone.hasFloorHold(getUniqueName()))
+        {
+            zone.setFloorHold(getUniqueName(), 0, false);
+        }
+        return;
+    }
+
     std::optional<uint64_t> newFloor;
 
     auto keyValue = getMaxGroupValue(*_keyGroup);