blob: c86e0e639a6ed864e73119915a031d7d225a9f48 [file] [log] [blame]
#include "topology.hpp"
#include "phosphor-logging/lg2.hpp"
const AssocName assocContaining =
AssocName("containing", "contained_by", {"Chassis"},
{"Board", "Chassis", "PowerSupply"});
const AssocName assocContainedBy = assocContaining.getReverse();
// Topology tests say that a chassis can be powering another chassis.
// In case there is any confusion as to why 'Chassis' can have 'powering'
// association.
const AssocName assocPowering =
AssocName("powering", "powered_by", {"Chassis", "PowerSupply"},
{"Board", "Chassis", "PowerSupply"});
const AssocName assocPoweredBy = assocPowering.getReverse();
const std::vector<AssocName> supportedAssocs = {
assocContaining,
assocContainedBy,
assocPowering,
assocPoweredBy,
};
AssocName::AssocName(const std::string& name, const std::string& reverse,
const std::set<std::string>& allowedOnBoardTypes,
const std::set<std::string>& allowedOnBoardTypesReverse) :
name(name), reverse(reverse), allowedOnBoardTypes(allowedOnBoardTypes),
allowedOnBoardTypesReverse(allowedOnBoardTypesReverse)
{}
AssocName AssocName::getReverse() const
{
return {reverse, name, allowedOnBoardTypesReverse, allowedOnBoardTypes};
}
bool AssocName::operator<(const AssocName& other) const
{
return name < other.name;
}
std::optional<AssocName> Topology::getAssocByName(const std::string& name)
{
for (const auto& assoc : supportedAssocs)
{
if (assoc.name == name)
{
return assoc;
}
}
return std::nullopt;
}
void Topology::addBoard(const std::string& path, const std::string& boardType,
const std::string& boardName,
const nlohmann::json& exposesItem)
{
auto findType = exposesItem.find("Type");
if (findType == exposesItem.end())
{
return;
}
boardNames.try_emplace(boardName, path);
PortType exposesType = findType->get<std::string>();
if (exposesType == "DownstreamPort")
{
addDownstreamPort(path, exposesItem);
}
else if (exposesType == "Port")
{
addConfiguredPort(path, exposesItem);
}
else if (exposesType.ends_with("Port"))
{
addPort(exposesType, path, assocContaining);
}
else
{
return;
}
boardTypes[path] = boardType;
}
void Topology::addConfiguredPort(const Path& path,
const nlohmann::json& exposesItem)
{
const auto findConnectsToName = exposesItem.find("Name");
if (findConnectsToName == exposesItem.end())
{
lg2::error("Board at path {PATH} is missing 'Name'", "PATH", path);
return;
}
const std::string connectsToName = findConnectsToName->get<std::string>();
const auto findPortType = exposesItem.find("PortType");
if (findPortType == exposesItem.end())
{
lg2::error("Board at path {PATH} is missing PortType", "PATH", path);
return;
}
const std::string portType = findPortType->get<std::string>();
const auto assoc = getAssocByName(portType);
if (!assoc.has_value())
{
lg2::error("Could not find configured association name {ASSOC}",
"ASSOC", portType);
return;
}
addPort(connectsToName, path, assoc.value());
}
void Topology::addDownstreamPort(const Path& path,
const nlohmann::json& exposesItem)
{
auto findConnectsTo = exposesItem.find("ConnectsToType");
if (findConnectsTo == exposesItem.end())
{
lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH",
path);
return;
}
PortType connectsTo = findConnectsTo->get<std::string>();
addPort(connectsTo, path, assocContainedBy);
auto findPoweredBy = exposesItem.find("PowerPort");
if (findPoweredBy != exposesItem.end())
{
addPort(connectsTo, path, assocPowering);
}
}
void Topology::addPort(const PortType& port, const Path& path,
const AssocName& assocName)
{
if (!ports.contains(port))
{
ports.insert({port, {}});
}
if (!ports[port].contains(path))
{
ports[port].insert({path, {}});
}
ports[port][path].insert(assocName);
}
std::unordered_map<std::string, std::set<Association>> Topology::getAssocs(
BoardPathsView boardPaths)
{
std::unordered_map<std::string, std::set<Association>> result;
// look at each upstream port type
for (const auto& port : ports)
{
fillAssocsForPortId(result, boardPaths, port.second);
}
return result;
}
void Topology::fillAssocsForPortId(
std::unordered_map<std::string, std::set<Association>>& result,
BoardPathsView boardPaths,
const std::map<Path, std::set<AssocName>>& pathAssocs)
{
for (const auto& member : pathAssocs)
{
for (const auto& other : pathAssocs)
{
if (other.first == member.first)
{
continue;
}
for (const auto& assocName : member.second)
{
// if the other end of the assocation does not declare
// the reverse association, do not associate
const bool otherAgrees =
other.second.contains(assocName.getReverse());
// quirk: since the other side of the association cannot declare
// to be powered_by in the legacy schema, in case of "powering",
// the two associations do not have to agree.
if (!otherAgrees && assocName != assocPowering)
{
continue;
}
fillAssocForPortId(result, boardPaths, member.first,
other.first, assocName);
}
}
}
}
void Topology::fillAssocForPortId(
std::unordered_map<std::string, std::set<Association>>& result,
BoardPathsView boardPaths, const Path& upstream, const Path& downstream,
const AssocName& assocName)
{
if (!assocName.allowedOnBoardTypes.contains(boardTypes[upstream]))
{
lg2::error(
"Cannot create Association Definition {ASSOC} for {PATH} with board type {TYPE}",
"ASSOC", assocName.name, "PATH", upstream, "TYPE",
boardTypes[upstream]);
return;
}
// The downstream path must be one we care about.
if (!std::ranges::contains(boardPaths, upstream))
{
return;
}
// quirk: legacy code did not associate from both sides
// TODO(alexander): revisit this
if (assocName == assocContaining || assocName == assocPoweredBy)
{
return;
}
result[upstream].insert({assocName.name, assocName.reverse, downstream});
}
void Topology::remove(const std::string& boardName)
{
// Remove the board from boardNames, and then using the path
// found in boardNames remove it from ports
auto boardFind = boardNames.find(boardName);
if (boardFind == boardNames.end())
{
return;
}
std::string boardPath = boardFind->second;
boardNames.erase(boardFind);
for (auto& port : ports)
{
port.second.erase(boardPath);
}
}