topology: Add powered_by topology

bmcweb already support the power supply command, refer to
https://gerrit.openbmc.org/c/openbmc/bmcweb/+/57668

Use "PowerPort" to add the powered_by association for upstream port, so
that the PowerSupplyCollection in redfish can get the power supply
information.

Tested:
Add the following config in mobo.json:
```
{
    "Name": "Mobo Upstream Port",
    "Type": "Mobo Upstream Port"
},
```
Add the following config in PSU.json:
```
{
    "ConnectsToType": "Mobo Upstream Port",
    "Name": "PSU $BUS Downstream Port",
    "Type": "DownstreamPort",
    "PowerPort": true
},
```
Result for association:
```
{
	"type" : "as",
	"data" : [
		[
			"/xyz/openbmc_project/inventory/system/board/PSU_1"
		]
	]
}
```
Result in bmcweb:
```
$ curl -s -k -H "X-Auth-Token: $token" http://${bmc}/redfish/v1/Chassis/Mobo | jq .Links.Contains
[
  {
    "@odata.id": "/redfish/v1/Chassis/PSU_1"
  }
]
$ curl -k -H "X-Auth-Token: $token" http://${bmc}/redfish/v1/Chassis/Mobo/PowerSubsystem/PowerSupplies/PSU_1
{
  "@odata.id": "/redfish/v1/Chassis/Mobo/PowerSubsystem/PowerSupplies/PSU_1",
  "@odata.type": "#PowerSupply.v1_5_0.PowerSupply",
  ...
}
```

Run unitest for test_topology is PASSED.

Change-Id: Iad10e61417437a41628cf311cdd7893725a5dcde
Signed-off-by: Jeff Lin <JeffLin2@quantatw.com>
diff --git a/src/topology.cpp b/src/topology.cpp
index f2ee22b..94389b9 100644
--- a/src/topology.cpp
+++ b/src/topology.cpp
@@ -29,6 +29,11 @@
 
         downstreamPorts[connectsTo].emplace_back(path);
         boardTypes[path] = boardType;
+        auto findPoweredBy = exposesItem.find("PowerPort");
+        if (findPoweredBy != exposesItem.end())
+        {
+            powerPaths.insert(path);
+        }
     }
     else if (exposesType.ends_with("Port"))
     {
@@ -65,6 +70,11 @@
                     {
                         result[downstream].emplace_back("contained_by",
                                                         "containing", upstream);
+                        if (powerPaths.find(downstream) != powerPaths.end())
+                        {
+                            result[upstream].emplace_back(
+                                "powered_by", "powering", downstream);
+                        }
                     }
                 }
             }
diff --git a/src/topology.hpp b/src/topology.hpp
index 4ea5246..467a5ca 100644
--- a/src/topology.hpp
+++ b/src/topology.hpp
@@ -27,6 +27,7 @@
 
     std::unordered_map<PortType, std::vector<Path>> upstreamPorts;
     std::unordered_map<PortType, std::vector<Path>> downstreamPorts;
+    std::set<Path> powerPaths;
     std::unordered_map<Path, BoardType> boardTypes;
     std::unordered_map<BoardName, Path> boardNames;
 };
diff --git a/test/test_topology.cpp b/test/test_topology.cpp
index f9beee3..e23c802 100644
--- a/test/test_topology.cpp
+++ b/test/test_topology.cpp
@@ -12,6 +12,8 @@
 
 const Association subchassisAssoc =
     std::make_tuple("contained_by", "containing", superchassisPath);
+const Association powerAssoc = std::make_tuple("powered_by", "powering",
+                                               subchassisPath);
 
 const nlohmann::json subchassisExposesItem = nlohmann::json::parse(R"(
     {
@@ -21,6 +23,15 @@
     }
 )");
 
+const nlohmann::json powerExposesItem = nlohmann::json::parse(R"(
+    {
+        "ConnectsToType": "BackplanePort",
+        "Name": "MyDownstreamPort",
+        "Type": "DownstreamPort",
+        "PowerPort": true
+    }
+)");
+
 const nlohmann::json superchassisExposesItem = nlohmann::json::parse(R"(
     {
         "Name": "MyBackplanePort",
@@ -138,6 +149,24 @@
     EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
 }
 
+TEST(Topology, BasicPower)
+{
+    Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
+
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", powerExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
+
+    auto assocs = topo.getAssocs(boards);
+
+    EXPECT_EQ(assocs.size(), 2);
+    EXPECT_EQ(assocs[subchassisPath].size(), 1);
+    EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
+    EXPECT_EQ(assocs[superchassisPath].size(), 1);
+    EXPECT_EQ(assocs[superchassisPath][0], powerAssoc);
+}
+
 TEST(Topology, NoNewBoards)
 {
     Topology topo;