1 #include "entity_manager/topology.hpp"
2
3 #include <ranges>
4
5 #include "gmock/gmock.h"
6 #include "gtest/gtest.h"
7
8 using ::testing::UnorderedElementsAre;
9
10 const std::string subchassisPath =
11 "/xyz/openbmc_project/inventory/system/chassis/Subchassis";
12 const std::string superchassisPath =
13 "/xyz/openbmc_project/inventory/system/chassis/Superchassis";
14
15 const Association subchassisAssoc =
16 std::make_tuple("contained_by", "containing", superchassisPath);
17 const Association powerAssoc =
18 std::make_tuple("powering", "powered_by", superchassisPath);
19
20 const nlohmann::json subchassisExposesItem = nlohmann::json::parse(R"(
21 {
22 "ConnectsToType": "BackplanePort",
23 "Name": "MyDownstreamPort",
24 "Type": "DownstreamPort"
25 }
26 )");
27
28 const nlohmann::json powerExposesItem = nlohmann::json::parse(R"(
29 {
30 "ConnectsToType": "BackplanePort",
31 "Name": "MyDownstreamPort",
32 "Type": "DownstreamPort",
33 "PowerPort": true
34 }
35 )");
36
37 const nlohmann::json superchassisExposesItem = nlohmann::json::parse(R"(
38 {
39 "Name": "MyBackplanePort",
40 "Type": "BackplanePort"
41 }
42 )");
43
44 const nlohmann::json otherExposesItem = nlohmann::json::parse(R"(
45 {
46 "Name": "MyExposes",
47 "Type": "OtherType"
48 }
49 )");
50
51 using BoardMap = std::map<std::string, std::string>;
52
TEST(Topology,Empty)53 TEST(Topology, Empty)
54 {
55 Topology topo;
56 BoardMap boards;
57
58 auto assocs = topo.getAssocs(std::views::keys(boards));
59
60 EXPECT_EQ(assocs.size(), 0U);
61 }
62
TEST(Topology,EmptyExposes)63 TEST(Topology, EmptyExposes)
64 {
65 Topology topo;
66 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
67
68 topo.addBoard(subchassisPath, "Chassis", "BoardA", nlohmann::json());
69 topo.addBoard(superchassisPath, "Chassis", "BoardB", nlohmann::json());
70
71 auto assocs = topo.getAssocs(std::views::keys(boards));
72
73 EXPECT_EQ(assocs.size(), 0U);
74 }
75
TEST(Topology,MissingConnectsTo)76 TEST(Topology, MissingConnectsTo)
77 {
78 const nlohmann::json subchassisMissingConnectsTo = nlohmann::json::parse(R"(
79 {
80 "Name": "MyDownstreamPort",
81 "Type": "DownstreamPort"
82 }
83 )");
84
85 Topology topo;
86 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
87
88 topo.addBoard(subchassisPath, "Chassis", "BoardA",
89 subchassisMissingConnectsTo);
90 topo.addBoard(superchassisPath, "Chassis", "BoardB",
91 superchassisExposesItem);
92
93 auto assocs = topo.getAssocs(std::views::keys(boards));
94
95 EXPECT_EQ(assocs.size(), 0U);
96 }
97
TEST(Topology,OtherExposes)98 TEST(Topology, OtherExposes)
99 {
100 Topology topo;
101 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
102
103 topo.addBoard(subchassisPath, "Chassis", "BoardA", otherExposesItem);
104 topo.addBoard(superchassisPath, "Chassis", "BoardB", otherExposesItem);
105
106 auto assocs = topo.getAssocs(std::views::keys(boards));
107
108 EXPECT_EQ(assocs.size(), 0U);
109 }
110
TEST(Topology,NoMatchSubchassis)111 TEST(Topology, NoMatchSubchassis)
112 {
113 Topology topo;
114 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
115
116 topo.addBoard(subchassisPath, "Chassis", "BoardA", otherExposesItem);
117 topo.addBoard(superchassisPath, "Chassis", "BoardB",
118 superchassisExposesItem);
119
120 auto assocs = topo.getAssocs(std::views::keys(boards));
121
122 EXPECT_EQ(assocs.size(), 0U);
123 }
124
TEST(Topology,NoMatchSuperchassis)125 TEST(Topology, NoMatchSuperchassis)
126 {
127 Topology topo;
128 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
129
130 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
131 topo.addBoard(superchassisPath, "Chassis", "BoardB", otherExposesItem);
132
133 auto assocs = topo.getAssocs(std::views::keys(boards));
134
135 EXPECT_EQ(assocs.size(), 0U);
136 }
137
TEST(Topology,Basic)138 TEST(Topology, Basic)
139 {
140 Topology topo;
141 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
142
143 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
144 topo.addBoard(superchassisPath, "Chassis", "BoardB",
145 superchassisExposesItem);
146
147 auto assocs = topo.getAssocs(std::views::keys(boards));
148
149 EXPECT_EQ(assocs.size(), 1U);
150 EXPECT_EQ(assocs[subchassisPath].size(), 1U);
151 EXPECT_TRUE(assocs[subchassisPath].contains(subchassisAssoc));
152 }
153
TEST(Topology,BasicPower)154 TEST(Topology, BasicPower)
155 {
156 Topology topo;
157 BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
158
159 topo.addBoard(subchassisPath, "Chassis", "BoardA", powerExposesItem);
160 topo.addBoard(superchassisPath, "Chassis", "BoardB",
161 superchassisExposesItem);
162
163 auto assocs = topo.getAssocs(std::views::keys(boards));
164
165 EXPECT_EQ(assocs.size(), 1U);
166 EXPECT_EQ(assocs[subchassisPath].size(), 2U);
167 EXPECT_TRUE(assocs[subchassisPath].contains(subchassisAssoc));
168 EXPECT_TRUE(assocs[subchassisPath].contains(powerAssoc));
169 }
170
TEST(Topology,NoNewBoards)171 TEST(Topology, NoNewBoards)
172 {
173 Topology topo;
174 BoardMap boards;
175
176 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
177 topo.addBoard(superchassisPath, "Chassis", "BoardB",
178 superchassisExposesItem);
179
180 // Boards A and B aren't new, so no assocs are returned.
181 auto assocs = topo.getAssocs(std::views::keys(boards));
182
183 EXPECT_EQ(assocs.size(), 0U);
184 }
185
186 TEST(Topology, 2Subchassis)
187 {
188 Topology topo;
189 BoardMap boards{{subchassisPath, "BoardA"},
190 {subchassisPath + "2", "BoardB"},
191 {superchassisPath, "BoardC"}};
192
193 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
194 topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
195 subchassisExposesItem);
196 topo.addBoard(superchassisPath, "Chassis", "BoardC",
197 superchassisExposesItem);
198
199 auto assocs = topo.getAssocs(std::views::keys(boards));
200
201 EXPECT_EQ(assocs.size(), 2U);
202 EXPECT_EQ(assocs[subchassisPath].size(), 1U);
203 EXPECT_TRUE(assocs[subchassisPath].contains(subchassisAssoc));
204 EXPECT_EQ(assocs[subchassisPath + "2"].size(), 1U);
205 EXPECT_TRUE(assocs[subchassisPath + "2"].contains(subchassisAssoc));
206 }
207
TEST(Topology,OneNewBoard)208 TEST(Topology, OneNewBoard)
209 {
210 Topology topo;
211 BoardMap boards{{subchassisPath, "BoardA"}};
212
213 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
214 topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
215 subchassisExposesItem);
216 topo.addBoard(superchassisPath, "Chassis", "BoardC",
217 superchassisExposesItem);
218
219 // Only the assoc for BoardA should be returned
220 auto assocs = topo.getAssocs(std::views::keys(boards));
221
222 EXPECT_EQ(assocs.size(), 1U);
223 EXPECT_EQ(assocs[subchassisPath].size(), 1U);
224 EXPECT_TRUE(assocs[subchassisPath].contains(subchassisAssoc));
225 }
226
227 TEST(Topology, 2Superchassis)
228 {
229 Association subchassisAssoc2 =
230 std::make_tuple("contained_by", "containing", superchassisPath + "2");
231
232 Topology topo;
233 BoardMap boards{{subchassisPath, "BoardA"},
234 {superchassisPath, "BoardB"},
235 {superchassisPath + "2", "BoardC"}};
236
237 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
238 topo.addBoard(superchassisPath, "Chassis", "BoardB",
239 superchassisExposesItem);
240 topo.addBoard(superchassisPath + "2", "Chassis", "BoardC",
241 superchassisExposesItem);
242
243 auto assocs = topo.getAssocs(std::views::keys(boards));
244
245 EXPECT_EQ(assocs.size(), 1U);
246 EXPECT_EQ(assocs[subchassisPath].size(), 2U);
247
248 EXPECT_THAT(assocs[subchassisPath],
249 UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
250 }
251
252 TEST(Topology, 2SuperchassisAnd2Subchassis)
253 {
254 Association subchassisAssoc2 =
255 std::make_tuple("contained_by", "containing", superchassisPath + "2");
256
257 Topology topo;
258 BoardMap boards{{subchassisPath, "BoardA"},
259 {subchassisPath + "2", "BoardB"},
260 {superchassisPath, "BoardC"},
261 {superchassisPath + "2", "BoardD"}};
262
263 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
264 topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
265 subchassisExposesItem);
266 topo.addBoard(superchassisPath, "Chassis", "BoardC",
267 superchassisExposesItem);
268 topo.addBoard(superchassisPath + "2", "Chassis", "BoardD",
269 superchassisExposesItem);
270
271 auto assocs = topo.getAssocs(std::views::keys(boards));
272
273 EXPECT_EQ(assocs.size(), 2U);
274 EXPECT_EQ(assocs[subchassisPath].size(), 2U);
275 EXPECT_EQ(assocs[subchassisPath + "2"].size(), 2U);
276
277 EXPECT_THAT(assocs[subchassisPath],
278 UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
279 EXPECT_THAT(assocs[subchassisPath + "2"],
280 UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
281 }
282
TEST(Topology,Remove)283 TEST(Topology, Remove)
284 {
285 Topology topo;
286 BoardMap boards{{subchassisPath, "BoardA"},
287 {subchassisPath + "2", "BoardB"},
288 {superchassisPath, "BoardC"}};
289
290 topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
291 topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
292 subchassisExposesItem);
293 topo.addBoard(superchassisPath, "Chassis", "BoardC",
294 superchassisExposesItem);
295
296 {
297 auto assocs = topo.getAssocs(std::views::keys(boards));
298
299 EXPECT_EQ(assocs.size(), 2U);
300 EXPECT_EQ(assocs[subchassisPath].size(), 1U);
301 EXPECT_TRUE(assocs[subchassisPath].contains(subchassisAssoc));
302 EXPECT_EQ(assocs[subchassisPath + "2"].size(), 1U);
303 EXPECT_TRUE(assocs[subchassisPath + "2"].contains(subchassisAssoc));
304 }
305
306 {
307 topo.remove("BoardA");
308 auto assocs = topo.getAssocs(std::views::keys(boards));
309
310 EXPECT_EQ(assocs.size(), 1U);
311 EXPECT_EQ(assocs[subchassisPath + "2"].size(), 1U);
312 EXPECT_TRUE(assocs[subchassisPath + "2"].contains(subchassisAssoc));
313 }
314
315 {
316 topo.remove("BoardB");
317 auto assocs = topo.getAssocs(std::views::keys(boards));
318
319 EXPECT_EQ(assocs.size(), 0U);
320 }
321 }
322
TEST(Topology,SimilarToTyanS8030)323 TEST(Topology, SimilarToTyanS8030)
324 {
325 const std::string chassisPath =
326 "/xyz/openbmc_project/inventory/system/chassis/ChassisA";
327 const std::string boardPath =
328 "/xyz/openbmc_project/inventory/system/board/BoardA";
329 const std::string psu0Path =
330 "/xyz/openbmc_project/inventory/system/powersupply/PSU0";
331 const std::string psu1Path =
332 "/xyz/openbmc_project/inventory/system/powersupply/PSU1";
333
334 const nlohmann::json chassisContainExposesItem = nlohmann::json::parse(R"(
335 {
336 "Name": "GenericContainPort",
337 "PortType": "containing",
338 "Type": "Port"
339 }
340 )");
341 const nlohmann::json containedByExposesItem = nlohmann::json::parse(R"(
342 {
343 "Name": "GenericContainPort",
344 "PortType": "contained_by",
345 "Type": "Port"
346 }
347 )");
348 const nlohmann::json boardPowerExposesItem = nlohmann::json::parse(R"(
349 {
350 "Name": "GenericPowerPort",
351 "PortType": "powered_by",
352 "Type": "Port"
353 }
354 )");
355 const nlohmann::json psuPowerExposesItem = nlohmann::json::parse(R"(
356 {
357 "Name": "GenericPowerPort",
358 "PortType": "powering",
359 "Type": "Port"
360 }
361 )");
362 Topology topo;
363 BoardMap boards{
364 {chassisPath, "ChassisA"},
365 {boardPath, "BoardA"},
366 {psu0Path, "PSU0"},
367 {psu1Path, "PSU1"},
368 };
369
370 // configure the chassis to be containing something
371 topo.addBoard(chassisPath, "Chassis", "ChassisA",
372 chassisContainExposesItem);
373
374 // configure board to be contained by something
375 topo.addBoard(boardPath, "Board", "BoardA", containedByExposesItem);
376
377 // configure the board to be powered by something
378 topo.addBoard(boardPath, "Board", "BoardA", boardPowerExposesItem);
379
380 // configure the PSUs to be powering something
381 topo.addBoard(psu0Path, "PowerSupply", "PSU0", psuPowerExposesItem);
382 topo.addBoard(psu1Path, "PowerSupply", "PSU1", psuPowerExposesItem);
383
384 // configured PSUs to be contained by something
385 topo.addBoard(psu0Path, "PowerSupply", "PSU0", containedByExposesItem);
386 topo.addBoard(psu1Path, "PowerSupply", "PSU1", containedByExposesItem);
387
388 auto assocs = topo.getAssocs(std::views::keys(boards));
389
390 EXPECT_TRUE(assocs.contains(boardPath));
391 EXPECT_TRUE(assocs.contains(psu0Path));
392 EXPECT_TRUE(assocs.contains(psu0Path));
393
394 // expect chassis to contain board
395 EXPECT_EQ(assocs[boardPath].size(), 1);
396 EXPECT_TRUE(assocs[boardPath].contains(
397 {"contained_by", "containing", chassisPath}));
398
399 // expect powering association from each PSU to the board
400 // and expect each PSU to be contained by the chassis
401 EXPECT_EQ(assocs[psu0Path].size(), 2);
402 EXPECT_TRUE(
403 assocs[psu0Path].contains({"powering", "powered_by", boardPath}));
404 EXPECT_TRUE(
405 assocs[psu0Path].contains({"contained_by", "containing", chassisPath}));
406
407 EXPECT_EQ(assocs[psu1Path].size(), 2);
408 EXPECT_TRUE(
409 assocs[psu1Path].contains({"powering", "powered_by", boardPath}));
410 EXPECT_TRUE(
411 assocs[psu1Path].contains({"contained_by", "containing", chassisPath}));
412 }
413
makeExposesItem(const std::string & name,const std::string & assocName)414 static nlohmann::json makeExposesItem(const std::string& name,
415 const std::string& assocName)
416 {
417 nlohmann::json exposesItem = nlohmann::json::parse(R"(
418 {
419 "Name": "REPLACE",
420 "PortType": "REPLACE",
421 "Type": "Port"
422 }
423 )");
424
425 exposesItem["Name"] = name;
426 exposesItem["PortType"] = assocName;
427
428 return exposesItem;
429 }
430
makeContainedPortExposesItem(const std::string & name)431 static nlohmann::json makeContainedPortExposesItem(const std::string& name)
432 {
433 return makeExposesItem(name, "contained_by");
434 }
435
makeContainingPortExposesItem(const std::string & name)436 static nlohmann::json makeContainingPortExposesItem(const std::string& name)
437 {
438 return makeExposesItem(name, "containing");
439 }
440
TEST(Topology,SimilarToYosemiteV3)441 TEST(Topology, SimilarToYosemiteV3)
442 {
443 const std::string blade1Path =
444 "/xyz/openbmc_project/inventory/system/board/Blade1";
445 const std::string blade2Path =
446 "/xyz/openbmc_project/inventory/system/board/Blade2";
447 const std::string blade3Path =
448 "/xyz/openbmc_project/inventory/system/board/Blade3";
449 const std::string blade4Path =
450 "/xyz/openbmc_project/inventory/system/board/Blade4";
451 const std::string chassis1Path =
452 "/xyz/openbmc_project/inventory/system/chassis/Blade1Chassis";
453 const std::string chassis2Path =
454 "/xyz/openbmc_project/inventory/system/chassis/Blade2Chassis";
455 const std::string chassis3Path =
456 "/xyz/openbmc_project/inventory/system/chassis/Blade3Chassis";
457 const std::string chassis4Path =
458 "/xyz/openbmc_project/inventory/system/chassis/Blade4Chassis";
459 const std::string superChassisPath =
460 "/xyz/openbmc_project/inventory/system/chassis/SuperChassis";
461
462 const nlohmann::json blade1ExposesItem =
463 makeContainedPortExposesItem("Blade1Port");
464 const nlohmann::json blade2ExposesItem =
465 makeContainedPortExposesItem("Blade2Port");
466 const nlohmann::json blade3ExposesItem =
467 makeContainedPortExposesItem("Blade3Port");
468 const nlohmann::json blade4ExposesItem =
469 makeContainedPortExposesItem("Blade4Port");
470
471 const nlohmann::json chassis1ExposesItem =
472 makeContainingPortExposesItem("Blade1Port");
473 const nlohmann::json chassis2ExposesItem =
474 makeContainingPortExposesItem("Blade2Port");
475 const nlohmann::json chassis3ExposesItem =
476 makeContainingPortExposesItem("Blade3Port");
477 const nlohmann::json chassis4ExposesItem =
478 makeContainingPortExposesItem("Blade4Port");
479
480 const nlohmann::json chassis1ExposesItem2 =
481 makeContainedPortExposesItem("SuperChassisPort");
482 const nlohmann::json chassis2ExposesItem2 =
483 makeContainedPortExposesItem("SuperChassisPort");
484 const nlohmann::json chassis3ExposesItem2 =
485 makeContainedPortExposesItem("SuperChassisPort");
486 const nlohmann::json chassis4ExposesItem2 =
487 makeContainedPortExposesItem("SuperChassisPort");
488
489 const nlohmann::json superChassisExposesItem =
490 makeContainingPortExposesItem("SuperChassisPort");
491
492 Topology topo;
493 BoardMap boards{
494 {blade1Path, "Blade1"},
495 {blade2Path, "Blade2"},
496 {blade3Path, "Blade3"},
497 {blade4Path, "Blade4"},
498 {chassis1Path, "Chassis1"},
499 {chassis2Path, "Chassis2"},
500 {chassis3Path, "Chassis3"},
501 {chassis4Path, "Chassis4"},
502 {superChassisPath, "SuperChassis"},
503 };
504
505 topo.addBoard(blade1Path, "Board", "Blade1", blade1ExposesItem);
506 topo.addBoard(blade2Path, "Board", "Blade2", blade2ExposesItem);
507 topo.addBoard(blade3Path, "Board", "Blade3", blade3ExposesItem);
508 topo.addBoard(blade4Path, "Board", "Blade4", blade4ExposesItem);
509
510 topo.addBoard(chassis1Path, "Chassis", "Chassis1", chassis1ExposesItem);
511 topo.addBoard(chassis2Path, "Chassis", "Chassis2", chassis2ExposesItem);
512 topo.addBoard(chassis3Path, "Chassis", "Chassis3", chassis3ExposesItem);
513 topo.addBoard(chassis4Path, "Chassis", "Chassis4", chassis4ExposesItem);
514
515 topo.addBoard(chassis1Path, "Chassis", "Chassis1", chassis1ExposesItem2);
516 topo.addBoard(chassis2Path, "Chassis", "Chassis2", chassis2ExposesItem2);
517 topo.addBoard(chassis3Path, "Chassis", "Chassis3", chassis3ExposesItem2);
518 topo.addBoard(chassis4Path, "Chassis", "Chassis4", chassis4ExposesItem2);
519
520 topo.addBoard(superChassisPath, "Chassis", "SuperChassis",
521 superChassisExposesItem);
522
523 auto assocs = topo.getAssocs(std::views::keys(boards));
524
525 // all blades are contained by their respective chassis
526 EXPECT_EQ(assocs[blade1Path].size(), 1);
527 EXPECT_TRUE(assocs[blade1Path].contains(
528 {"contained_by", "containing", chassis1Path}));
529
530 EXPECT_EQ(assocs[blade2Path].size(), 1);
531 EXPECT_TRUE(assocs[blade2Path].contains(
532 {"contained_by", "containing", chassis2Path}));
533
534 EXPECT_EQ(assocs[blade3Path].size(), 1);
535 EXPECT_TRUE(assocs[blade3Path].contains(
536 {"contained_by", "containing", chassis3Path}));
537
538 EXPECT_EQ(assocs[blade4Path].size(), 1);
539 EXPECT_TRUE(assocs[blade4Path].contains(
540 {"contained_by", "containing", chassis4Path}));
541
542 // all chassis are contained by the superchassis
543 EXPECT_TRUE(assocs[chassis1Path].contains(
544 {"contained_by", "containing", superChassisPath}));
545
546 EXPECT_TRUE(assocs[chassis2Path].contains(
547 {"contained_by", "containing", superChassisPath}));
548
549 EXPECT_TRUE(assocs[chassis3Path].contains(
550 {"contained_by", "containing", superChassisPath}));
551
552 EXPECT_TRUE(assocs[chassis4Path].contains(
553 {"contained_by", "containing", superChassisPath}));
554 }
555