1 #include "topology.hpp"
2
3 #include "phosphor-logging/lg2.hpp"
4
5 const AssocName assocContaining =
6 AssocName("containing", "contained_by", {"Chassis"},
7 {"Board", "Chassis", "PowerSupply"});
8 const AssocName assocContainedBy = assocContaining.getReverse();
9
10 // Topology tests say that a chassis can be powering another chassis.
11 // In case there is any confusion as to why 'Chassis' can have 'powering'
12 // association.
13 const AssocName assocPowering =
14 AssocName("powering", "powered_by", {"Chassis", "PowerSupply"},
15 {"Board", "Chassis", "PowerSupply"});
16 const AssocName assocPoweredBy = assocPowering.getReverse();
17
18 const std::vector<AssocName> supportedAssocs = {
19 assocContaining,
20 assocContainedBy,
21 assocPowering,
22 assocPoweredBy,
23 };
24
AssocName(const std::string & name,const std::string & reverse,const std::set<std::string> & allowedOnBoardTypes,const std::set<std::string> & allowedOnBoardTypesReverse)25 AssocName::AssocName(const std::string& name, const std::string& reverse,
26 const std::set<std::string>& allowedOnBoardTypes,
27 const std::set<std::string>& allowedOnBoardTypesReverse) :
28 name(name), reverse(reverse), allowedOnBoardTypes(allowedOnBoardTypes),
29 allowedOnBoardTypesReverse(allowedOnBoardTypesReverse)
30 {}
31
getReverse() const32 AssocName AssocName::getReverse() const
33 {
34 return {reverse, name, allowedOnBoardTypesReverse, allowedOnBoardTypes};
35 }
36
operator <(const AssocName & other) const37 bool AssocName::operator<(const AssocName& other) const
38 {
39 return name < other.name;
40 }
41
getAssocByName(const std::string & name)42 std::optional<AssocName> Topology::getAssocByName(const std::string& name)
43 {
44 for (const auto& assoc : supportedAssocs)
45 {
46 if (assoc.name == name)
47 {
48 return assoc;
49 }
50 }
51 return std::nullopt;
52 }
53
addBoard(const std::string & path,const std::string & boardType,const std::string & boardName,const nlohmann::json & exposesItem)54 void Topology::addBoard(const std::string& path, const std::string& boardType,
55 const std::string& boardName,
56 const nlohmann::json& exposesItem)
57 {
58 auto findType = exposesItem.find("Type");
59 if (findType == exposesItem.end())
60 {
61 return;
62 }
63
64 boardNames.try_emplace(boardName, path);
65
66 PortType exposesType = findType->get<std::string>();
67
68 if (exposesType == "DownstreamPort")
69 {
70 addDownstreamPort(path, exposesItem);
71 }
72 else if (exposesType == "Port")
73 {
74 addConfiguredPort(path, exposesItem);
75 }
76 else if (exposesType.ends_with("Port"))
77 {
78 addPort(exposesType, path, assocContaining);
79
80 // this represents the legacy quirk of upstream ports having no choice
81 // in the
82 // powered_by association
83 addPort(exposesType, path, assocPoweredBy);
84 }
85 else
86 {
87 return;
88 }
89 boardTypes[path] = boardType;
90 }
91
addConfiguredPort(const Path & path,const nlohmann::json & exposesItem)92 void Topology::addConfiguredPort(const Path& path,
93 const nlohmann::json& exposesItem)
94 {
95 const auto findConnectsToName = exposesItem.find("Name");
96 if (findConnectsToName == exposesItem.end())
97 {
98 lg2::error("Board at path {PATH} is missing 'Name'", "PATH", path);
99 return;
100 }
101 const std::string connectsToName = findConnectsToName->get<std::string>();
102
103 const auto findPortType = exposesItem.find("PortType");
104 if (findPortType == exposesItem.end())
105 {
106 lg2::error("Board at path {PATH} is missing PortType", "PATH", path);
107 return;
108 }
109 const std::string portType = findPortType->get<std::string>();
110
111 const auto assoc = getAssocByName(portType);
112 if (!assoc.has_value())
113 {
114 lg2::error("Could not find configured association name {ASSOC}",
115 "ASSOC", portType);
116 return;
117 }
118
119 addPort(connectsToName, path, assoc.value());
120 }
121
addDownstreamPort(const Path & path,const nlohmann::json & exposesItem)122 void Topology::addDownstreamPort(const Path& path,
123 const nlohmann::json& exposesItem)
124 {
125 auto findConnectsTo = exposesItem.find("ConnectsToType");
126 if (findConnectsTo == exposesItem.end())
127 {
128 lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH",
129 path);
130 return;
131 }
132 PortType connectsTo = findConnectsTo->get<std::string>();
133
134 addPort(connectsTo, path, assocContainedBy);
135
136 auto findPoweredBy = exposesItem.find("PowerPort");
137 if (findPoweredBy != exposesItem.end())
138 {
139 addPort(connectsTo, path, assocPowering);
140 }
141 }
142
addPort(const PortType & port,const Path & path,const AssocName & assocName)143 void Topology::addPort(const PortType& port, const Path& path,
144 const AssocName& assocName)
145 {
146 if (!ports.contains(port))
147 {
148 ports.insert({port, {}});
149 }
150 if (!ports[port].contains(path))
151 {
152 ports[port].insert({path, {}});
153 }
154 ports[port][path].insert(assocName);
155 }
156
getAssocs(BoardPathsView boardPaths)157 std::unordered_map<std::string, std::set<Association>> Topology::getAssocs(
158 BoardPathsView boardPaths)
159 {
160 std::unordered_map<std::string, std::set<Association>> result;
161
162 // look at each upstream port type
163 for (const auto& port : ports)
164 {
165 fillAssocsForPortId(result, boardPaths, port.second);
166 }
167 return result;
168 }
169
fillAssocsForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const std::map<Path,std::set<AssocName>> & pathAssocs)170 void Topology::fillAssocsForPortId(
171 std::unordered_map<std::string, std::set<Association>>& result,
172 BoardPathsView boardPaths,
173 const std::map<Path, std::set<AssocName>>& pathAssocs)
174 {
175 for (const auto& member : pathAssocs)
176 {
177 for (const auto& other : pathAssocs)
178 {
179 if (other.first == member.first)
180 {
181 continue;
182 }
183 for (const auto& assocName : member.second)
184 {
185 // if the other end of the assocation does not declare
186 // the reverse association, do not associate
187 const bool otherAgrees =
188 other.second.contains(assocName.getReverse());
189
190 if (!otherAgrees)
191 {
192 continue;
193 }
194
195 fillAssocForPortId(result, boardPaths, member.first,
196 other.first, assocName);
197 }
198 }
199 }
200 }
201
fillAssocForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const Path & upstream,const Path & downstream,const AssocName & assocName)202 void Topology::fillAssocForPortId(
203 std::unordered_map<std::string, std::set<Association>>& result,
204 BoardPathsView boardPaths, const Path& upstream, const Path& downstream,
205 const AssocName& assocName)
206 {
207 if (!assocName.allowedOnBoardTypes.contains(boardTypes[upstream]))
208 {
209 lg2::error(
210 "Cannot create Association Definition {ASSOC} for {PATH} with board type {TYPE}",
211 "ASSOC", assocName.name, "PATH", upstream, "TYPE",
212 boardTypes[upstream]);
213 return;
214 }
215 // The downstream path must be one we care about.
216 if (!std::ranges::contains(boardPaths, upstream))
217 {
218 return;
219 }
220
221 // quirk: legacy code did not associate from both sides
222 // TODO(alexander): revisit this
223 if (assocName == assocContaining || assocName == assocPoweredBy)
224 {
225 return;
226 }
227
228 result[upstream].insert({assocName.name, assocName.reverse, downstream});
229 }
230
remove(const std::string & boardName)231 void Topology::remove(const std::string& boardName)
232 {
233 // Remove the board from boardNames, and then using the path
234 // found in boardNames remove it from ports
235 auto boardFind = boardNames.find(boardName);
236 if (boardFind == boardNames.end())
237 {
238 return;
239 }
240
241 std::string boardPath = boardFind->second;
242
243 boardNames.erase(boardFind);
244
245 for (auto& port : ports)
246 {
247 port.second.erase(boardPath);
248 }
249 }
250