blob: b39bc4abcaf1db29bd54b74b2b331d77871069f2 [file] [log] [blame]
/**
* 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 "signal.hpp"
#include "../manager.hpp"
#include "action.hpp"
#include "group.hpp"
#include "handlers.hpp"
#include "trigger_aliases.hpp"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus/match.hpp>
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
#include <numeric>
#include <utility>
#include <vector>
namespace phosphor::fan::control::json::trigger::signal
{
using json = nlohmann::json;
using namespace phosphor::logging;
using namespace sdbusplus::bus::match;
void subscribe(const std::string& match, SignalPkg&& signalPkg,
std::function<bool(SignalPkg&)> isSameSig, Manager* mgr)
{
auto& signalData = mgr->getSignal(match);
if (signalData.empty())
{
// Signal subscription doesnt exist, add signal package and subscribe
std::vector<SignalPkg> pkgs = {signalPkg};
std::vector<SignalPkg> dataPkgs =
std::vector<SignalPkg>(std::move(pkgs));
std::unique_ptr<sdbusplus::server::match::match> ptrMatch = nullptr;
// TODO(ibm-openbmc/#3195) - Filter signal subscriptions to objects
// owned by fan control?
if (!match.empty())
{
// Subscribe to signal
ptrMatch = std::make_unique<sdbusplus::server::match::match>(
mgr->getBus(), match.c_str(),
std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr),
std::placeholders::_1, dataPkgs));
}
signalData.emplace_back(std::move(dataPkgs), std::move(ptrMatch));
}
else
{
// Signal subscription already exists
// Only a single signal data entry tied to each match is supported
auto& pkgs = std::get<std::vector<SignalPkg>>(signalData.front());
for (auto& pkg : pkgs)
{
if (isSameSig(pkg))
{
// Same signal expected, add actions to be run when signal
// received
auto& pkgActions = std::get<SignalActions>(signalPkg);
auto& actions = std::get<SignalActions>(pkg);
actions.insert(actions.end(),
std::make_move_iterator(pkgActions.begin()),
std::make_move_iterator(pkgActions.end()));
}
else
{
// Expected signal differs, add signal package
pkgs.emplace_back(std::move(signalPkg));
}
}
}
}
void propertiesChanged(Manager* mgr, const Group& group, SignalActions actions,
const json&)
{
// Groups are optional, but a signal triggered event with no groups
// will do nothing since signals require a group
for (const auto& member : group.getMembers())
{
// Setup property changed signal handler on the group member's
// property
const auto match =
rules::propertiesChanged(member, group.getInterface());
SignalPkg signalPkg = {Handlers::propertiesChanged,
SignalObject(std::cref(member),
std::cref(group.getInterface()),
std::cref(group.getProperty())),
SignalActions(actions)};
auto isSameSig = [&prop = group.getProperty()](SignalPkg& pkg) {
auto& obj = std::get<SignalObject>(pkg);
return prop == std::get<Prop>(obj);
};
subscribe(match, std::move(signalPkg), isSameSig, mgr);
}
}
void interfacesAdded(Manager* mgr, const Group& group, SignalActions actions,
const json&)
{
// Groups are optional, but a signal triggered event with no groups
// will do nothing since signals require a group
for (const auto& member : group.getMembers())
{
// Setup interfaces added signal handler on the group member
const auto match = rules::interfacesAdded(member);
SignalPkg signalPkg = {Handlers::interfacesAdded,
SignalObject(std::cref(member),
std::cref(group.getInterface()),
std::cref(group.getProperty())),
SignalActions(actions)};
auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) {
auto& obj = std::get<SignalObject>(pkg);
return intf == std::get<Intf>(obj);
};
subscribe(match, std::move(signalPkg), isSameSig, mgr);
}
}
void interfacesRemoved(Manager* mgr, const Group& group, SignalActions actions,
const json&)
{
// Groups are optional, but a signal triggered event with no groups
// will do nothing since signals require a group
for (const auto& member : group.getMembers())
{
// Setup interfaces added signal handler on the group member
const auto match = rules::interfacesRemoved(member);
SignalPkg signalPkg = {Handlers::interfacesRemoved,
SignalObject(std::cref(member),
std::cref(group.getInterface()),
std::cref(group.getProperty())),
SignalActions(actions)};
auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) {
auto& obj = std::get<SignalObject>(pkg);
return intf == std::get<Intf>(obj);
};
subscribe(match, std::move(signalPkg), isSameSig, mgr);
}
}
void nameOwnerChanged(Manager* mgr, const Group& group, SignalActions actions,
const json&)
{
// Groups are optional, but a signal triggered event with no groups
// will do nothing since signals require a group
for (const auto& member : group.getMembers())
{
auto serv = Manager::getService(member, group.getInterface());
if (!serv.empty())
{
// Setup name owner changed signal handler on the group
// member's service
const auto match = rules::nameOwnerChanged(serv);
SignalPkg signalPkg = {Handlers::nameOwnerChanged,
SignalObject(std::cref(member),
std::cref(group.getInterface()),
std::cref(group.getProperty())),
SignalActions(actions)};
// If signal match already exists, then the service will be the
// same so add action to be run
auto isSameSig = [](SignalPkg& pkg) { return true; };
subscribe(match, std::move(signalPkg), isSameSig, mgr);
}
else
{
// Unable to construct nameOwnerChanged match string
// Path and/or interface configured does not exist on dbus yet?
// TODO How to handle this? Create timer to keep checking for
// service to appear? When to stop checking?
log<level::ERR>(
fmt::format("Events will not be triggered by name owner changed"
"signals from service of path {}, interface {}",
member, group.getInterface())
.c_str());
}
}
}
void member(Manager* mgr, const Group&, SignalActions actions,
const json& jsonObj)
{
if (!jsonObj.contains("member") || !jsonObj["member"].contains("name") ||
!jsonObj["member"].contains("path") ||
!jsonObj["member"].contains("interface"))
{
log<level::ERR>("Missing required member trigger attributes",
entry("JSON=%s", jsonObj.dump().c_str()));
throw std::runtime_error("Missing required member trigger attributes");
}
const auto match =
rules::type::signal() +
rules::member(jsonObj["member"]["name"].get<std::string>()) +
rules::path(jsonObj["member"]["path"].get<std::string>()) +
rules::interface(jsonObj["member"]["interface"].get<std::string>());
// No SignalObject required to associate to this signal
SignalPkg signalPkg = {Handlers::member, SignalObject(),
SignalActions(actions)};
// If signal match already exists, then the member signal will be the
// same so add action to be run
auto isSameSig = [](SignalPkg& pkg) { return true; };
subscribe(match, std::move(signalPkg), isSameSig, mgr);
}
enableTrigger triggerSignal(const json& jsonObj, const std::string& eventName,
SignalActions actions)
{
auto subscriber = signals.end();
if (jsonObj.contains("signal"))
{
auto signal = jsonObj["signal"].get<std::string>();
std::transform(signal.begin(), signal.end(), signal.begin(), tolower);
subscriber = signals.find(signal);
}
if (subscriber == signals.end())
{
// Construct list of available signals
auto availSignals =
std::accumulate(std::next(signals.begin()), signals.end(),
signals.begin()->first, [](auto list, auto signal) {
return std::move(list) + ", " + signal.first;
});
auto msg =
fmt::format("Event '{}' requires a supported signal given to be "
"triggered by signal, available signals: {}",
eventName, availSignals);
log<level::ERR>(msg.c_str());
throw std::runtime_error(msg.c_str());
}
return [subscriber = std::move(subscriber),
jsonObj](const std::string& eventName, Manager* mgr,
const std::vector<Group>& groups,
std::vector<std::unique_ptr<ActionBase>>& actions) {
for (const auto& group : groups)
{
// Call signal subscriber for each group
subscriber->second(mgr, group, actions, jsonObj);
}
};
}
} // namespace phosphor::fan::control::json::trigger::signal