topology: generalized implementation
Topology code did not support adding arbitrary associations since the
original implementation was mostly about "contained_by" and "powering"
association was bolted on top using a special "powerPaths" collection.
This patch has 2 goals:
- Separate "contained_by" from "powering" association as actual systems
do not always follow this layout. There can be a PSU powering multiple
chassis, but not be contained by any of them.
- Enable arbitrary associations to be added to inventory items in the
future. None are added here, but the foundation is there and they do
not need a special treatment.
Main change:
```
+ std::unordered_map<PortType, std::map<Path, std::set<AssocName>>> ports;
- std::unordered_map<PortType, std::set<Path>> upstreamPorts;
- std::unordered_map<PortType, std::set<Path>> downstreamPorts;
- std::set<Path> powerPaths;
```
The main difference is that 'powerPaths' is no longer needed as the
underlying meaning (`powered_by` association) is stored in the
set<AssocName>.
The quirk of powerering => contained_by is implemented.
Tested: Topology Unit Test Pass.
Some quirks were added to make the tests pass since they correctly!
assert the limitations from the legacy code.
Reworking the tests to represent the capabilities of the new
implementation can be done in future patches, but for this one, the code
should work the same for the existing callers.
Change-Id: I7cfc752241403f0b661448299f1666f882fa8085
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/src/entity_manager/topology.cpp b/src/entity_manager/topology.cpp
index 8f7b7b1..16af9cd 100644
--- a/src/entity_manager/topology.cpp
+++ b/src/entity_manager/topology.cpp
@@ -22,7 +22,7 @@
}
else if (exposesType.ends_with("Port"))
{
- upstreamPorts[exposesType].insert(path);
+ addPort(exposesType, path, "containing");
}
else
{
@@ -43,85 +43,112 @@
}
PortType connectsTo = findConnectsTo->get<std::string>();
- downstreamPorts[connectsTo].insert(path);
+ addPort(connectsTo, path, "contained_by");
+
auto findPoweredBy = exposesItem.find("PowerPort");
if (findPoweredBy != exposesItem.end())
{
- powerPaths.insert(path);
+ addPort(connectsTo, path, "powering");
}
}
+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& upstreamPortPair : upstreamPorts)
+ for (const auto& port : ports)
{
- auto downstreamMatch = downstreamPorts.find(upstreamPortPair.first);
-
- if (downstreamMatch == downstreamPorts.end())
- {
- // no match
- continue;
- }
-
- fillAssocsForPortId(result, boardPaths, upstreamPortPair.second,
- downstreamMatch->second);
+ fillAssocsForPortId(result, boardPaths, port.second);
}
return result;
}
void Topology::fillAssocsForPortId(
std::unordered_map<std::string, std::set<Association>>& result,
- BoardPathsView boardPaths, const std::set<Path>& upstreamPaths,
- const std::set<Path>& downstreamPaths)
+ BoardPathsView boardPaths,
+ const std::map<Path, std::set<AssocName>>& pathAssocs)
{
- for (const Path& upstream : upstreamPaths)
+ for (const auto& member : pathAssocs)
{
- for (const Path& downstream : downstreamPaths)
+ for (const auto& other : pathAssocs)
{
- fillAssocForPortId(result, boardPaths, upstream, downstream);
+ if (other.first == member.first)
+ {
+ continue;
+ }
+ for (const auto& assocName : member.second)
+ {
+ auto optReverse = getOppositeAssoc(assocName);
+ if (!optReverse.has_value())
+ {
+ continue;
+ }
+ // if the other end of the assocation does not declare
+ // the reverse association, do not associate
+ const bool otherAgrees =
+ other.second.contains(optReverse.value());
+
+ // 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 != "powering")
+ {
+ 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)
+ BoardPathsView boardPaths, const Path& upstream, const Path& downstream,
+ const AssocName& assocName)
{
if (boardTypes[upstream] != "Chassis" && boardTypes[upstream] != "Board")
{
return;
}
// The downstream path must be one we care about.
- if (!std::ranges::contains(boardPaths, downstream))
+ if (!std::ranges::contains(boardPaths, upstream))
{
return;
}
- std::string assoc = "contained_by";
- std::optional<std::string> opposite = getOppositeAssoc(assoc);
+ auto optReverse = getOppositeAssoc(assocName);
- if (!opposite.has_value())
+ if (!optReverse)
{
return;
}
- result[downstream].insert({assoc, opposite.value(), upstream});
-
- if (powerPaths.contains(downstream))
+ // quirk: legacy code did not associate from both sides
+ // TODO(alexander): revisit this
+ if (assocName == "containing" || assocName == "powered_by")
{
- assoc = "powering";
- opposite = getOppositeAssoc(assoc);
- if (!opposite.has_value())
- {
- return;
- }
-
- result[downstream].insert({assoc, opposite.value(), upstream});
+ return;
}
+
+ result[upstream].insert({assocName, optReverse.value(), downstream});
}
const std::set<std::pair<std::string, std::string>> assocs = {
@@ -150,8 +177,7 @@
void Topology::remove(const std::string& boardName)
{
// Remove the board from boardNames, and then using the path
- // found in boardNames remove it from upstreamPorts and
- // downstreamPorts.
+ // found in boardNames remove it from ports
auto boardFind = boardNames.find(boardName);
if (boardFind == boardNames.end())
{
@@ -162,13 +188,8 @@
boardNames.erase(boardFind);
- for (auto& upstreamPort : upstreamPorts)
+ for (auto& port : ports)
{
- upstreamPort.second.erase(boardPath);
- }
-
- for (auto& downstreamPort : downstreamPorts)
- {
- downstreamPort.second.erase(boardPath);
+ port.second.erase(boardPath);
}
}
diff --git a/src/entity_manager/topology.hpp b/src/entity_manager/topology.hpp
index 39dcae7..9fc3536 100644
--- a/src/entity_manager/topology.hpp
+++ b/src/entity_manager/topology.hpp
@@ -37,20 +37,26 @@
// such as 'MB Upstream Port'
void fillAssocsForPortId(
std::unordered_map<std::string, std::set<Association>>& result,
- BoardPathsView boardPaths, const std::set<Path>& upstreamPaths,
- const std::set<Path>& downstreamPaths);
+ BoardPathsView boardPaths,
+ const std::map<Path, std::set<AssocName>>& pathAssocs);
void fillAssocForPortId(
std::unordered_map<std::string, std::set<Association>>& result,
- BoardPathsView boardPaths, const Path& upstream,
- const Path& downstream);
+ BoardPathsView boardPaths, const Path& upstream, const Path& downstream,
+ const AssocName& assocName);
+
+ void addPort(const PortType& port, const Path& path,
+ const AssocName& assocName);
static std::optional<std::string> getOppositeAssoc(
const AssocName& assocName);
- std::unordered_map<PortType, std::set<Path>> upstreamPorts;
- std::unordered_map<PortType, std::set<Path>> downstreamPorts;
- std::set<Path> powerPaths;
+ // Maps the port name to the participating paths.
+ // each path also has their role(s) in the association.
+ // For example a PSU path which is part of "MB Upstream Port"
+ // will have "powering" role.
+ std::unordered_map<PortType, std::map<Path, std::set<AssocName>>> ports;
+
std::unordered_map<Path, BoardType> boardTypes;
std::unordered_map<BoardName, Path> boardNames;
};