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/src/topology.cpp b/src/topology.cpp
index 02e7458..f2ee22b 100644
--- a/src/topology.cpp
+++ b/src/topology.cpp
@@ -3,6 +3,7 @@
 #include <iostream>
 
 void Topology::addBoard(const std::string& path, const std::string& boardType,
+                        const std::string& boardName,
                         const nlohmann::json& exposesItem)
 {
     auto findType = exposesItem.find("Type");
@@ -10,6 +11,9 @@
     {
         return;
     }
+
+    boardNames.try_emplace(boardName, path);
+
     PortType exposesType = findType->get<std::string>();
 
     if (exposesType == "DownstreamPort")
@@ -33,7 +37,8 @@
     }
 }
 
-std::unordered_map<std::string, std::vector<Association>> Topology::getAssocs()
+std::unordered_map<std::string, std::vector<Association>>
+    Topology::getAssocs(const std::map<Path, BoardName>& boards)
 {
     std::unordered_map<std::string, std::vector<Association>> result;
 
@@ -55,8 +60,12 @@
             {
                 for (const Path& downstream : downstreamMatch->second)
                 {
-                    result[downstream].emplace_back("contained_by",
-                                                    "containing", upstream);
+                    // The downstream path must be one we care about.
+                    if (boards.find(downstream) != boards.end())
+                    {
+                        result[downstream].emplace_back("contained_by",
+                                                        "containing", upstream);
+                    }
                 }
             }
         }
@@ -64,3 +73,57 @@
 
     return result;
 }
+
+void Topology::remove(const std::string& boardName)
+{
+    // Remove the board from boardNames, and then using the path
+    // found in boardNames remove it from upstreamPorts and
+    // downstreamPorts.
+    auto boardFind = boardNames.find(boardName);
+    if (boardFind == boardNames.end())
+    {
+        return;
+    }
+
+    std::string boardPath = boardFind->second;
+
+    boardNames.erase(boardFind);
+
+    for (auto it = upstreamPorts.begin(); it != upstreamPorts.end();)
+    {
+        auto pathIt = std::find(it->second.begin(), it->second.end(),
+                                boardPath);
+        if (pathIt != it->second.end())
+        {
+            it->second.erase(pathIt);
+        }
+
+        if (it->second.empty())
+        {
+            it = upstreamPorts.erase(it);
+        }
+        else
+        {
+            ++it;
+        }
+    }
+
+    for (auto it = downstreamPorts.begin(); it != downstreamPorts.end();)
+    {
+        auto pathIt = std::find(it->second.begin(), it->second.end(),
+                                boardPath);
+        if (pathIt != it->second.end())
+        {
+            it->second.erase(pathIt);
+        }
+
+        if (it->second.empty())
+        {
+            it = downstreamPorts.erase(it);
+        }
+        else
+        {
+            ++it;
+        }
+    }
+}