phys-topology: Add late add/remove support

The current physical topology code doesn't yet support adding entities
late (in a different propertiesChangedCallback call) or removing an
entity because:
- The Topology class is just scoped to postToDbus(), so when that is
  called again later with new cards it has no concept of existing parent
  cards so it will miss creating associations.

- There is nothing to tell the class when an entity is removed, so it
  never attempts to remove the association for that entity.

- When the containing/contained_by association is created it doesn't use
  the createInterface() function, so if that entity is removed later
  that association interface will be left on D-Bus.

To add support for entity adds and removes, this commit will:
- Make the Topology class have a global scoped lifetime so it can
  remember entity relationships.

- Now that Topology will outlive postToDbus() calls, pass the
  getAssocs() method the list of boards being processed in the current
  postToDbus() incantation so it will only return the new associations.

- Use the createInterface() method when creating the association. This
  stores the interface in a map with the entity name so that when the
  entity is removed the interface will be removed along with all the
  other interfaces.

- When an entity is removed, only the board name is known.  So pass the
  board name into addBoard() so the Topology class knows it, and add a
  Topology::remove() method and call it so it can remove the removed
  path from all of the connector maps.

Tested:
- All of the containing/contained_by associations still show up on good
  path.

- Added new unit tests to cover the new functionality.

- When a downstream entity is added after EM does its initial D-Bus
  publish, the containing/contained_by association is now created.

- On an entity remove, there are no left over interfaces for the removed
  entity on D-Bus.

- When the removed entity is added back, the association is put back in
  place.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ie5daaca92c6d2e6e7abc408f3e67e948977581ef
diff --git a/test/test_topology.cpp b/test/test_topology.cpp
index 76ab54a..f9beee3 100644
--- a/test/test_topology.cpp
+++ b/test/test_topology.cpp
@@ -35,11 +35,14 @@
     }
 )");
 
+using BoardMap = std::map<std::string, std::string>;
+
 TEST(Topology, Empty)
 {
     Topology topo;
+    BoardMap boards;
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -47,11 +50,12 @@
 TEST(Topology, EmptyExposes)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", nlohmann::json());
-    topo.addBoard(superchassisPath, "Chassis", nlohmann::json());
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", nlohmann::json());
+    topo.addBoard(superchassisPath, "Chassis", "BoardB", nlohmann::json());
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -66,11 +70,14 @@
     )");
 
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisMissingConnectsTo);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA",
+                  subchassisMissingConnectsTo);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -78,11 +85,12 @@
 TEST(Topology, OtherExposes)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", otherExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB", otherExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -90,11 +98,13 @@
 TEST(Topology, NoMatchSubchassis)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", otherExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -102,11 +112,12 @@
 TEST(Topology, NoMatchSuperchassis)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB", otherExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 0);
 }
@@ -114,26 +125,48 @@
 TEST(Topology, Basic)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}, {superchassisPath, "BoardB"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 1);
     EXPECT_EQ(assocs[subchassisPath].size(), 1);
     EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
 }
 
+TEST(Topology, NoNewBoards)
+{
+    Topology topo;
+    BoardMap boards;
+
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
+
+    // Boards A and B aren't new, so no assocs are returned.
+    auto assocs = topo.getAssocs(boards);
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
 TEST(Topology, 2Subchassis)
 {
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"},
+                    {subchassisPath + "2", "BoardB"},
+                    {superchassisPath, "BoardC"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
-    topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
+                  subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardC",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 2);
     EXPECT_EQ(assocs[subchassisPath].size(), 1);
@@ -142,18 +175,42 @@
     EXPECT_EQ(assocs[subchassisPath + "2"][0], subchassisAssoc);
 }
 
+TEST(Topology, OneNewBoard)
+{
+    Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"}};
+
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
+                  subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardC",
+                  superchassisExposesItem);
+
+    // Only the assoc for BoardA should be returned
+    auto assocs = topo.getAssocs(boards);
+
+    EXPECT_EQ(assocs.size(), 1);
+    EXPECT_EQ(assocs[subchassisPath].size(), 1);
+    EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
+}
+
 TEST(Topology, 2Superchassis)
 {
     const Association subchassisAssoc2 =
         std::make_tuple("contained_by", "containing", superchassisPath + "2");
 
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"},
+                    {superchassisPath, "BoardB"},
+                    {superchassisPath + "2", "BoardC"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
-    topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardB",
+                  superchassisExposesItem);
+    topo.addBoard(superchassisPath + "2", "Chassis", "BoardC",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 1);
     EXPECT_EQ(assocs[subchassisPath].size(), 2);
@@ -168,13 +225,20 @@
         std::make_tuple("contained_by", "containing", superchassisPath + "2");
 
     Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"},
+                    {subchassisPath + "2", "BoardB"},
+                    {superchassisPath, "BoardC"},
+                    {superchassisPath + "2", "BoardD"}};
 
-    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
-    topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
-    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
-    topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
+                  subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardC",
+                  superchassisExposesItem);
+    topo.addBoard(superchassisPath + "2", "Chassis", "BoardD",
+                  superchassisExposesItem);
 
-    auto assocs = topo.getAssocs();
+    auto assocs = topo.getAssocs(boards);
 
     EXPECT_EQ(assocs.size(), 2);
     EXPECT_EQ(assocs[subchassisPath].size(), 2);
@@ -185,3 +249,43 @@
     EXPECT_THAT(assocs[subchassisPath + "2"],
                 UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
 }
+
+TEST(Topology, Remove)
+{
+    Topology topo;
+    BoardMap boards{{subchassisPath, "BoardA"},
+                    {subchassisPath + "2", "BoardB"},
+                    {superchassisPath, "BoardC"}};
+
+    topo.addBoard(subchassisPath, "Chassis", "BoardA", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", "BoardB",
+                  subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", "BoardC",
+                  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[subchassisPath + "2"].size(), 1);
+        EXPECT_EQ(assocs[subchassisPath + "2"][0], subchassisAssoc);
+    }
+
+    {
+        topo.remove("BoardA");
+        auto assocs = topo.getAssocs(boards);
+
+        EXPECT_EQ(assocs.size(), 1);
+        EXPECT_EQ(assocs[subchassisPath + "2"].size(), 1);
+        EXPECT_EQ(assocs[subchassisPath + "2"][0], subchassisAssoc);
+    }
+
+    {
+        topo.remove("BoardB");
+        auto assocs = topo.getAssocs(boards);
+
+        EXPECT_EQ(assocs.size(), 0);
+    }
+}