Move to subproject to enable both projects

Yocto builds need each component to build individually, but meson wants
them to build as a single project. This follows google-misc and moves
to subprojects.

It also enables callback-manager in the build and fixes some build
errors and warnings.

Change-Id: Ie56141bf86b6d9c6b27eb697944fbc392e374c22
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/subprojects/callback-manager/.gitignore b/subprojects/callback-manager/.gitignore
new file mode 100644
index 0000000..0cb1d79
--- /dev/null
+++ b/subprojects/callback-manager/.gitignore
@@ -0,0 +1,3 @@
+build/*
+oe-logs
+oe-workdir
diff --git a/subprojects/callback-manager/LICENCE b/subprojects/callback-manager/LICENCE
new file mode 100644
index 0000000..729f4d4
--- /dev/null
+++ b/subprojects/callback-manager/LICENCE
@@ -0,0 +1,13 @@
+Copyright 2019 Intel 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.
diff --git a/subprojects/callback-manager/README.md b/subprojects/callback-manager/README.md
new file mode 100644
index 0000000..6d23787
--- /dev/null
+++ b/subprojects/callback-manager/README.md
@@ -0,0 +1,75 @@
+# Callback Manager
+
+Author: James Feist !jfei
+
+Primary assignee: James Feist !jfei
+
+Other contributors: None
+
+Created: 2019-06-26
+
+## Problem Description
+
+We need a centralized location to change the LED state.
+
+## Background and References
+
+[Redfish Health](https://github.com/openbmc/docs/blob/master/designs/redfish_health_rollup.md)
+
+[Sensor Thresholds](https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/xyz/openbmc_project/Sensor/Threshold)
+
+## Requirements
+
+Need to be able to trigger the LED from multiple locations and maintain state.
+
+## Proposed Design
+
+The callback manager can change LED state in the below ways.
+
+1. Monitoring the sensor threshold interface.
+1. Monitoring critical / warning thresholds as Redfish does.
+
+Other interfaces specific interfaces can be added later if it makes sense. The
+reason it was designed this way is Redfish has the idea of a Global health
+state, and a component health state. We need to map these to the LED.
+
+### Thresholds
+
+Warning thresholds will trigger warning (blink green) LED state.
+
+Critical thresholds will trigger critical (blink amber) state.
+
+Callback manager will create associations for redfish for thresholds
+automatically.
+
+### Associations
+
+There are two types of status associations, detailed in the Redfish health
+whitepaper.
+
+1. Global- These are the attributes to the global state (warning / critical).
+   This is exposed by the callback manager for ease
+   (xyz.openbmc_project.Inventory.Item.Global,
+   path=/xyz/openbmc_project/CallbackManager)
+1. Local- Any other warning / critical association.
+
+If the path for an association is Critical for Global & Local, the LED will be
+in the fatal (amber solid) state.
+
+If the path for an association is in Warning for Global & Critical for local,
+the LED will be in the Critical (amber blink) state.
+
+If the path for an association is in the Warning for Global & not Critical for
+local, the LED will be in the Warning (blink green) state.
+
+## Alternatives Considered
+
+Write the LED manually, this seems very error prone.
+
+## Impacts
+
+Will have to add more associations.
+
+## Testing
+
+Sensor override, then look at LED and Redfish.
diff --git a/subprojects/callback-manager/include/callback_manager.hpp b/subprojects/callback-manager/include/callback_manager.hpp
new file mode 100644
index 0000000..e4776c8
--- /dev/null
+++ b/subprojects/callback-manager/include/callback_manager.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <iostream>
+
+using Association = std::tuple<std::string, std::string, std::string>;
+
+constexpr const char* rootPath = "/xyz/openbmc_project/CallbackManager";
+constexpr const char* sensorPath = "/xyz/openbmc_project/sensors";
+
+constexpr const char* globalInventoryIface =
+    "xyz.openbmc_project.Inventory.Item.Global";
+constexpr const char* associationIface =
+    "xyz.openbmc_project.Association.Definitions";
+
+namespace threshold
+{
+constexpr const char* critical = "critical";
+constexpr const char* warning = "warning";
+} // namespace threshold
+
+struct AssociationManager
+{
+    AssociationManager(sdbusplus::asio::object_server& objectServerIn,
+                       std::shared_ptr<sdbusplus::asio::connection>& /*conn*/) :
+        objectServer(objectServerIn),
+        association(objectServer.add_interface(rootPath, associationIface)),
+        sensorAssociation(
+            objectServer.add_interface(sensorPath, associationIface))
+    {
+        association->register_property("Associations", std::set<Association>());
+        sensorAssociation->register_property("Associations",
+                                             std::set<Association>());
+        association->initialize();
+        sensorAssociation->initialize();
+    }
+    ~AssociationManager()
+    {
+        objectServer.remove_interface(association);
+        objectServer.remove_interface(sensorAssociation);
+    }
+
+    void setLocalAssociations(const std::vector<std::string>& fatal,
+                              const std::vector<std::string>& critical,
+                              const std::vector<std::string>& warning)
+    {
+        std::set<Association> result;
+
+        // fatal maps to redfish critical as refish only has 3 states and LED
+        // has 4
+        for (const std::string& path : fatal)
+        {
+            result.emplace(threshold::critical, "", path);
+        }
+        for (const std::string& path : critical)
+        {
+            result.emplace(threshold::warning, "", path);
+        }
+        for (const std::string& path : warning)
+        {
+            result.emplace(threshold::warning, "", path);
+        }
+        association->set_property("Associations", result);
+    }
+
+    void setSensorAssociations(const std::vector<std::string>& critical,
+                               const std::vector<std::string>& warning)
+    {
+        std::set<Association> result;
+        for (const std::string& path : critical)
+        {
+            if (!boost::starts_with(path, sensorPath))
+            {
+                continue;
+            }
+            result.emplace(threshold::critical, "", path);
+        }
+        for (const std::string& path : warning)
+        {
+            if (!boost::starts_with(path, sensorPath))
+            {
+                continue;
+            }
+            result.emplace(threshold::warning, "", path);
+        }
+        sensorAssociation->set_property("Associations", result);
+    }
+
+    sdbusplus::asio::object_server& objectServer;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> sensorAssociation;
+};
diff --git a/subprojects/callback-manager/meson.build b/subprojects/callback-manager/meson.build
new file mode 100644
index 0000000..78bd22b
--- /dev/null
+++ b/subprojects/callback-manager/meson.build
@@ -0,0 +1,66 @@
+project(
+    'callback-manager',
+    'cpp',
+    version: '1.1.1',
+    meson_version: '>=1.1.1',
+    default_options: ['cpp_std=c++23'],
+)
+
+# Compiler flags
+cpp_args = [
+    '-Werror',
+    '-Wall',
+    '-Wextra',
+    '-Wshadow',
+    '-Wnon-virtual-dtor',
+    '-Wold-style-cast',
+    '-Wcast-align',
+    '-Wunused',
+    '-Woverloaded-virtual',
+    '-Wpedantic',
+    '-Wconversion',
+    '-Wmisleading-indentation',
+    '-Wduplicated-cond',
+    '-Wduplicated-branches',
+    '-Wlogical-op',
+    '-Wnull-dereference',
+    '-Wuseless-cast',
+    '-Wdouble-promotion',
+    '-Wformat=2',
+    '-fno-rtti',
+]
+
+# Definitions
+add_project_arguments(
+    '-DBOOST_ERROR_CODE_HEADER_ONLY',
+    '-DBOOST_SYSTEM_NO_DEPRECATED',
+    '-DBOOST_ALL_NO_LIB',
+    '-DBOOST_NO_RTTI',
+    '-DBOOST_NO_TYPEID',
+    '-DBOOST_ASIO_DISABLE_THREADS',
+    language: 'cpp',
+)
+
+# Include directories
+inc = include_directories('include')
+
+boost = dependency('boost', version: '1.86.0', required: false)
+sdbusplus = dependency('sdbusplus', required: true)
+
+executable(
+    'callback-manager',
+    'src/callback_manager.cpp',
+    include_directories: inc,
+    cpp_args: cpp_args,
+    dependencies: [boost, sdbusplus],
+)
+
+# Systemd service files
+systemd_system_unit_dir = dependency('systemd').get_variable(
+    'systemdsystemunitdir',
+)
+
+install_data(
+    'service_files/callback-manager.service',
+    install_dir: systemd_system_unit_dir,
+)
diff --git a/subprojects/callback-manager/service_files/callback-manager.service b/subprojects/callback-manager/service_files/callback-manager.service
new file mode 100644
index 0000000..3b82395
--- /dev/null
+++ b/subprojects/callback-manager/service_files/callback-manager.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Callback Manager
+StopWhenUnneeded=false
+After=xyz.openbmc_project.LED.GroupManager.service
+
+[Service]
+Type=dbus
+BusName=xyz.openbmc_project.CallbackManager
+Restart=always
+RestartSec=5
+ExecStart=/usr/sbin/callback-manager
+
+[Install]
+WantedBy=multi-user.target
diff --git a/subprojects/callback-manager/src/callback_manager.cpp b/subprojects/callback-manager/src/callback_manager.cpp
new file mode 100644
index 0000000..4d54ffe
--- /dev/null
+++ b/subprojects/callback-manager/src/callback_manager.cpp
@@ -0,0 +1,384 @@
+/*
+// Copyright (c) 2019 Intel 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 "callback_manager.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <variant>
+
+constexpr const char* fatalLedPath =
+    "/xyz/openbmc_project/led/groups/status_critical";
+constexpr const char* criticalLedPath =
+    "/xyz/openbmc_project/led/groups/status_non_critical";
+constexpr const char* warningLedPath =
+    "/xyz/openbmc_project/led/groups/status_degraded";
+constexpr const char* okLedPath = "/xyz/openbmc_project/led/groups/status_ok";
+
+constexpr const char* ledIface = "xyz.openbmc_project.Led.Group";
+constexpr const char* ledAssertProp = "Asserted";
+constexpr const char* ledManagerBusname =
+    "xyz.openbmc_project.LED.GroupManager";
+
+std::unique_ptr<AssociationManager> associationManager;
+
+enum class StatusSetting
+{
+    none,
+    ok,
+    warn,
+    critical,
+    fatal
+};
+
+constexpr const bool debug = false;
+
+// final led state tracking
+StatusSetting currentPriority = StatusSetting::none;
+
+// maps of <object-path, <property, asserted>>
+boost::container::flat_map<std::string,
+                           boost::container::flat_map<std::string, bool>>
+    fatalAssertMap;
+boost::container::flat_map<std::string,
+                           boost::container::flat_map<std::string, bool>>
+    criticalAssertMap;
+boost::container::flat_map<std::string,
+                           boost::container::flat_map<std::string, bool>>
+    warningAssertMap;
+
+std::vector<std::string> assertedInMap(
+    const boost::container::flat_map<
+        std::string, boost::container::flat_map<std::string, bool>>& map)
+{
+    std::vector<std::string> ret;
+    // if any of the properties are true, return true
+    for (const auto& pair : map)
+    {
+        for (const auto& item : pair.second)
+        {
+            if (item.second)
+            {
+                ret.push_back(pair.first);
+            }
+        }
+    }
+    return ret;
+}
+
+void updateLedStatus(std::shared_ptr<sdbusplus::asio::connection>& conn,
+                     bool forceRefresh = false)
+{
+    std::vector<std::string> fatalVector = assertedInMap(fatalAssertMap);
+    bool fatal = fatalVector.size();
+
+    std::vector<std::string> criticalVector = assertedInMap(criticalAssertMap);
+    bool critical = criticalVector.size();
+
+    std::vector<std::string> warningVector = assertedInMap(warningAssertMap);
+    bool warn = warningVector.size();
+
+    associationManager->setLocalAssociations(fatalVector, criticalVector,
+                                             warningVector);
+
+    StatusSetting last = currentPriority;
+    std::vector<std::pair<std::string, std::variant<bool>>> ledsToSet;
+    if (forceRefresh)
+    {
+        ledsToSet.push_back(std::make_pair(fatalLedPath, false));
+        ledsToSet.push_back(std::make_pair(criticalLedPath, false));
+        ledsToSet.push_back(std::make_pair(warningLedPath, false));
+        ledsToSet.push_back(std::make_pair(okLedPath, false));
+        for (const auto& ledPair : ledsToSet)
+        {
+            conn->async_method_call(
+                [ledPair](const boost::system::error_code ec) {
+                    std::ios_base::fmtflags originalFlags = std::cerr.flags();
+                    if (ec)
+                    {
+                        std::cerr << "Cannot set " << ledPair.first << " to "
+                                  << std::boolalpha
+                                  << std::get<bool>(ledPair.second) << "\n";
+                        std::cerr.flags(originalFlags);
+                    }
+                    if constexpr (debug)
+                    {
+                        std::cerr << "Set " << ledPair.first << " to "
+                                  << std::boolalpha
+                                  << std::get<bool>(ledPair.second) << "\n";
+                        std::cerr.flags(originalFlags);
+                    }
+                },
+                ledManagerBusname, ledPair.first,
+                "org.freedesktop.DBus.Properties", "Set", ledIface,
+                ledAssertProp, ledPair.second);
+        }
+    }
+    if (fatal)
+    {
+        currentPriority = StatusSetting::fatal;
+    }
+    else if (critical)
+    {
+        currentPriority = StatusSetting::critical;
+    }
+    else if (warn)
+    {
+        currentPriority = StatusSetting::warn;
+    }
+    else
+    {
+        currentPriority = StatusSetting::ok;
+    }
+
+    if (last != currentPriority || forceRefresh)
+    {
+        switch (currentPriority)
+        {
+            case (StatusSetting::fatal):
+            {
+                ledsToSet.push_back(std::make_pair(fatalLedPath, true));
+                ledsToSet.push_back(std::make_pair(criticalLedPath, false));
+                ledsToSet.push_back(std::make_pair(warningLedPath, false));
+                ledsToSet.push_back(std::make_pair(okLedPath, false));
+                break;
+            }
+            case (StatusSetting::critical):
+            {
+                ledsToSet.push_back(std::make_pair(fatalLedPath, false));
+                ledsToSet.push_back(std::make_pair(criticalLedPath, true));
+                ledsToSet.push_back(std::make_pair(warningLedPath, false));
+                ledsToSet.push_back(std::make_pair(okLedPath, false));
+                break;
+            }
+            case (StatusSetting::warn):
+            {
+                ledsToSet.push_back(std::make_pair(fatalLedPath, false));
+                ledsToSet.push_back(std::make_pair(criticalLedPath, false));
+                ledsToSet.push_back(std::make_pair(warningLedPath, true));
+                ledsToSet.push_back(std::make_pair(okLedPath, false));
+                break;
+            }
+            case (StatusSetting::ok):
+            {
+                ledsToSet.push_back(std::make_pair(fatalLedPath, false));
+                ledsToSet.push_back(std::make_pair(criticalLedPath, false));
+                ledsToSet.push_back(std::make_pair(warningLedPath, false));
+                ledsToSet.push_back(std::make_pair(okLedPath, true));
+                break;
+            }
+            case (StatusSetting::none):
+            default:
+                break;
+        }
+    }
+
+    for (const auto& ledPair : ledsToSet)
+    {
+        conn->async_method_call(
+            [ledPair](const boost::system::error_code ec) {
+                if (ec)
+                {
+                    std::cerr << "Cannot set " << ledPair.first << " to "
+                              << std::boolalpha
+                              << std::get<bool>(ledPair.second) << "\n";
+                }
+                if constexpr (debug)
+                {
+                    std::cerr
+                        << "Set " << ledPair.first << " to " << std::boolalpha
+                        << std::get<bool>(ledPair.second) << "\n";
+                }
+            },
+            ledManagerBusname, ledPair.first, "org.freedesktop.DBus.Properties",
+            "Set", ledIface, ledAssertProp, ledPair.second);
+    }
+}
+
+void createThresholdMatch(std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    static sdbusplus::bus::match_t match(
+        static_cast<sdbusplus::bus_t&>(*conn),
+        "type='signal',member='ThresholdAsserted'",
+        [&conn](sdbusplus::message_t& message) {
+            std::string sensorName;
+            std::string thresholdInterface;
+            std::string event;
+            bool assert;
+            double assertValue;
+
+            try
+            {
+                message.read(sensorName, thresholdInterface, event, assert,
+                             assertValue);
+            }
+            catch (sdbusplus::exception_t&)
+            {
+                return;
+            }
+            if constexpr (debug)
+            {
+                std::cerr << "Threshold callback: SensorName = " << sensorName
+                          << ", Event = " << event << ", Asserted = " << assert
+                          << "\n";
+            }
+
+            if (event == "CriticalAlarmLow")
+            {
+                criticalAssertMap[message.get_path()]["low"] = assert;
+            }
+            else if (event == "CriticalAlarmHigh")
+            {
+                criticalAssertMap[message.get_path()]["high"] = assert;
+            }
+            else if (event == "WarningAlarmLow")
+            {
+                warningAssertMap[message.get_path()]["low"] = assert;
+            }
+            else if (event == "WarningAlarmHigh")
+            {
+                warningAssertMap[message.get_path()]["high"] = assert;
+            }
+
+            associationManager->setSensorAssociations(
+                assertedInMap(criticalAssertMap),
+                assertedInMap(warningAssertMap));
+
+            updateLedStatus(conn);
+        });
+}
+
+void createAssociationMatch(std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    static sdbusplus::bus::match_t match(
+        static_cast<sdbusplus::bus_t&>(*conn),
+        "type='signal',interface='org.freedesktop.DBus.Properties',"
+        "arg0namespace='" +
+            std::string(associationIface) + "'",
+        [&conn](sdbusplus::message_t& message) {
+            if (message.get_path() == rootPath)
+            {
+                return; // it's us
+            }
+            std::string objectName;
+            boost::container::flat_map<std::string,
+                                       std::variant<std::vector<Association>>>
+                values;
+            try
+            {
+                message.read(objectName, values);
+            }
+            catch (sdbusplus::exception_t&)
+            {
+                return;
+            }
+
+            if constexpr (debug)
+            {
+                std::cerr << "Association callback " << message.get_path()
+                          << "\n";
+            }
+
+            auto findAssociations = values.find("Associations");
+            if (findAssociations == values.end())
+            {
+                return;
+            }
+            const std::vector<Association>* associations =
+                std::get_if<std::vector<Association>>(
+                    &findAssociations->second);
+
+            if (associations == nullptr)
+            {
+                std::cerr << "Illegal Association on " << message.get_path()
+                          << "\n";
+                return;
+            }
+
+            bool localWarning = false;
+            bool localCritical = false;
+            bool globalWarning = false;
+            bool globalCritical = false;
+
+            for (const auto& [forward, reverse, path] : *associations)
+            {
+                if (path == rootPath)
+                {
+                    globalWarning = globalWarning ? true : reverse == "warning";
+                    globalCritical =
+                        globalCritical ? true : reverse == "critical";
+
+                    if constexpr (1)
+                    {
+                        std::cerr << "got global ";
+                    }
+                }
+                else
+                {
+                    localWarning = localWarning ? true : reverse == "warning";
+                    localCritical =
+                        localCritical ? true : reverse == "critical";
+                }
+                if (globalCritical && localCritical)
+                {
+                    break;
+                }
+            }
+
+            bool fatal = globalCritical && localCritical;
+            bool critical = globalWarning && localCritical;
+            bool warning = globalWarning && !critical;
+
+            fatalAssertMap[message.get_path()]["association"] = fatal;
+            criticalAssertMap[message.get_path()]["association"] = critical;
+            warningAssertMap[message.get_path()]["association"] = warning;
+
+            updateLedStatus(conn);
+        });
+}
+
+int main(int /*argc*/, char** /*argv*/)
+{
+    boost::asio::io_context io;
+    auto conn = std::make_shared<sdbusplus::asio::connection>(io);
+    conn->request_name("xyz.openbmc_project.CallbackManager");
+    sdbusplus::asio::object_server objServer(conn);
+    std::shared_ptr<sdbusplus::asio::dbus_interface> rootIface =
+        objServer.add_interface(rootPath,
+                                "xyz.openbmc_project.CallbackManager");
+    rootIface->register_method("RetriggerLEDUpdate", [&conn]() {
+        updateLedStatus(conn, true);
+    });
+    rootIface->initialize();
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
+        objServer.add_interface(rootPath, globalInventoryIface);
+    inventoryIface->initialize();
+
+    associationManager = std::make_unique<AssociationManager>(objServer, conn);
+
+    createThresholdMatch(conn);
+    createAssociationMatch(conn);
+    updateLedStatus(conn);
+
+    io.run();
+
+    return 0;
+}