xref: /openbmc/entity-manager/test/test_topology.cpp (revision 8ca094098fee469d47043068f1be45eb035550b0)
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