topology: add initial module
To use this module, add information about boards and their exposes
records and then request a map of what resulting associations should be
exposed on D-Bus.
Tested: Unit tests pass
Signed-off-by: Benjamin Fair <benjaminfair@google.com>
Change-Id: I42cd79407ab476599f5010c11cc146a569694b24
diff --git a/include/topology.hpp b/include/topology.hpp
new file mode 100644
index 0000000..18c9244
--- /dev/null
+++ b/include/topology.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+#include <set>
+#include <unordered_map>
+
+using Association = std::tuple<std::string, std::string, std::string>;
+
+class Topology
+{
+ public:
+ explicit Topology() = default;
+
+ void addBoard(const std::string& path, const std::string& boardType,
+ const nlohmann::json& exposesItem);
+ std::unordered_map<std::string, std::vector<Association>> getAssocs();
+
+ private:
+ using Path = std::string;
+ using BoardType = std::string;
+ using PortType = std::string;
+
+ std::unordered_map<PortType, std::vector<Path>> upstreamPorts;
+ std::unordered_map<PortType, std::vector<Path>> downstreamPorts;
+ std::unordered_map<Path, BoardType> boardTypes;
+};
diff --git a/meson.build b/meson.build
index 105606f..05ade13 100644
--- a/meson.build
+++ b/meson.build
@@ -199,12 +199,15 @@
if not build_tests.disabled()
test_boost_args = boost_args + ['-DBOOST_ASIO_DISABLE_THREADS']
gtest = dependency('gtest', main: true, disabler: true, required: false)
- if not gtest.found() and build_tests.enabled()
+ gmock = dependency('gmock', disabler: true, required: false)
+ if not (gtest.found() and gmock.found()) and build_tests.enabled()
cmake = import('cmake')
gtest_subproject = cmake.subproject('gtest')
cm_gtest = gtest_subproject.dependency('gtest')
cm_gtest_main = gtest_subproject.dependency('gtest_main')
gtest = declare_dependency(dependencies: [cm_gtest, cm_gtest_main, threads])
+ gmock = gtest_subproject.dependency('gmock')
+
endif
test(
@@ -243,4 +246,20 @@
include_directories: 'include',
)
)
+
+ test(
+ 'test_topology',
+ executable(
+ 'test_topology',
+ 'test/test_topology.cpp',
+ 'src/topology.cpp',
+ cpp_args: test_boost_args,
+ dependencies: [
+ gtest,
+ gmock,
+ ],
+ implicit_include_directories: false,
+ include_directories: 'include',
+ )
+ )
endif
diff --git a/src/topology.cpp b/src/topology.cpp
new file mode 100644
index 0000000..02e7458
--- /dev/null
+++ b/src/topology.cpp
@@ -0,0 +1,66 @@
+#include "topology.hpp"
+
+#include <iostream>
+
+void Topology::addBoard(const std::string& path, const std::string& boardType,
+ const nlohmann::json& exposesItem)
+{
+ auto findType = exposesItem.find("Type");
+ if (findType == exposesItem.end())
+ {
+ return;
+ }
+ PortType exposesType = findType->get<std::string>();
+
+ if (exposesType == "DownstreamPort")
+ {
+ auto findConnectsTo = exposesItem.find("ConnectsToType");
+ if (findConnectsTo == exposesItem.end())
+ {
+ std::cerr << "Board at path " << path
+ << " is missing ConnectsToType" << std::endl;
+ return;
+ }
+ PortType connectsTo = findConnectsTo->get<std::string>();
+
+ downstreamPorts[connectsTo].emplace_back(path);
+ boardTypes[path] = boardType;
+ }
+ else if (exposesType.ends_with("Port"))
+ {
+ upstreamPorts[exposesType].emplace_back(path);
+ boardTypes[path] = boardType;
+ }
+}
+
+std::unordered_map<std::string, std::vector<Association>> Topology::getAssocs()
+{
+ std::unordered_map<std::string, std::vector<Association>> result;
+
+ // look at each upstream port type
+ for (const auto& upstreamPortPair : upstreamPorts)
+ {
+ auto downstreamMatch = downstreamPorts.find(upstreamPortPair.first);
+
+ if (downstreamMatch == downstreamPorts.end())
+ {
+ // no match
+ continue;
+ }
+
+ for (const Path& upstream : upstreamPortPair.second)
+ {
+ if (boardTypes[upstream] == "Chassis" ||
+ boardTypes[upstream] == "Board")
+ {
+ for (const Path& downstream : downstreamMatch->second)
+ {
+ result[downstream].emplace_back("contained_by",
+ "containing", upstream);
+ }
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/test/test_topology.cpp b/test/test_topology.cpp
new file mode 100644
index 0000000..76ab54a
--- /dev/null
+++ b/test/test_topology.cpp
@@ -0,0 +1,187 @@
+#include "topology.hpp"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::UnorderedElementsAre;
+
+const std::string subchassisPath =
+ "/xyz/openbmc_project/inventory/system/chassis/Subchassis";
+const std::string superchassisPath =
+ "/xyz/openbmc_project/inventory/system/chassis/Superchassis";
+
+const Association subchassisAssoc =
+ std::make_tuple("contained_by", "containing", superchassisPath);
+
+const nlohmann::json subchassisExposesItem = nlohmann::json::parse(R"(
+ {
+ "ConnectsToType": "BackplanePort",
+ "Name": "MyDownstreamPort",
+ "Type": "DownstreamPort"
+ }
+)");
+
+const nlohmann::json superchassisExposesItem = nlohmann::json::parse(R"(
+ {
+ "Name": "MyBackplanePort",
+ "Type": "BackplanePort"
+ }
+)");
+
+const nlohmann::json otherExposesItem = nlohmann::json::parse(R"(
+ {
+ "Name": "MyExposes",
+ "Type": "OtherType"
+ }
+)");
+
+TEST(Topology, Empty)
+{
+ Topology topo;
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, EmptyExposes)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", nlohmann::json());
+ topo.addBoard(superchassisPath, "Chassis", nlohmann::json());
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, MissingConnectsTo)
+{
+ const nlohmann::json subchassisMissingConnectsTo = nlohmann::json::parse(R"(
+ {
+ "Name": "MyDownstreamPort",
+ "Type": "DownstreamPort"
+ }
+ )");
+
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisMissingConnectsTo);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, OtherExposes)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, NoMatchSubchassis)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, NoMatchSuperchassis)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, Basic)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 1);
+ EXPECT_EQ(assocs[subchassisPath].size(), 1);
+ EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
+}
+
+TEST(Topology, 2Subchassis)
+{
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+ topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ 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);
+}
+
+TEST(Topology, 2Superchassis)
+{
+ const Association subchassisAssoc2 =
+ std::make_tuple("contained_by", "containing", superchassisPath + "2");
+
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+ topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 1);
+ EXPECT_EQ(assocs[subchassisPath].size(), 2);
+
+ EXPECT_THAT(assocs[subchassisPath],
+ UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+}
+
+TEST(Topology, 2SuperchassisAnd2Subchassis)
+{
+ const Association subchassisAssoc2 =
+ std::make_tuple("contained_by", "containing", superchassisPath + "2");
+
+ Topology topo;
+
+ topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+ topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
+ topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+ topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+
+ auto assocs = topo.getAssocs();
+
+ EXPECT_EQ(assocs.size(), 2);
+ EXPECT_EQ(assocs[subchassisPath].size(), 2);
+ EXPECT_EQ(assocs[subchassisPath + "2"].size(), 2);
+
+ EXPECT_THAT(assocs[subchassisPath],
+ UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+ EXPECT_THAT(assocs[subchassisPath + "2"],
+ UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+}