mapper: Add Associated subtree method definition
dbus-interface change in:
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/57821
Create new mapper function to make subtree call and association call at
the same time. With the association definition, we will have more
endpoints to fetch and verify that it is actually the resource that we
are looking for. We need to verify that it has the expected interface to
confirm that it is right device. In the current workflow this will take
two dbus calls to the mapper to get the association and the subtree.
This change aims to reduce that down to one by combining the two
operations. It will not significant performance increase for the daemons,
but it can help reduce the load on the dbus-broker.service.
Tested:
```
Normal Call:
busctl call "xyz.openbmc_project.ObjectMapper" \
"/xyz/openbmc_project/object_mapper" \
"xyz.openbmc_project.ObjectMapper" "GetSubTreePaths" sias \
"/xyz/openbmc_project/inventory" 0 1 \
"xyz.openbmc_project.Inventory.Item.Storage"
as 4 "/xyz/openbmc_project/inventory/storage_0" "/xyz/openbmc_project/inventory/storage_1"
"/xyz/openbmc_project/inventory/storage_4" "/xyz/openbmc_project/inventory/storage_3"
Associated Call: (Only 0 and 1 is associated to the chassis)
busctl call "xyz.openbmc_project.ObjectMapper" \
"/xyz/openbmc_project/object_mapper" \
"xyz.openbmc_project.ObjectMapper" "GetAssociatedSubTreePaths" ssias \
"/xyz/openbmc_project/inventory/chassis_0/storage" \
"/xyz/openbmc_project/inventory" 0 1 "xyz.openbmc_project.Inventory.Item.Storage"
as 2 "/xyz/openbmc_project/inventory/storage_0" "/xyz/openbmc_project/inventory/storage_1"
```
Change-Id: Ib532e660cf20b9efc83045fb1cbd9143d0c1841e
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/src/handler.cpp b/src/handler.cpp
index 1a362ab..a1efb84 100644
--- a/src/handler.cpp
+++ b/src/handler.cpp
@@ -6,6 +6,7 @@
#include <algorithm>
#include <string>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -275,3 +276,63 @@
return ret;
}
+
+std::vector<InterfaceMapType::value_type>
+ getAssociatedSubTree(const InterfaceMapType& interfaceMap,
+ const AssociationMaps& associationMaps,
+ const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath,
+ int32_t depth, std::vector<std::string>& interfaces)
+{
+ auto findEndpoint = associationMaps.ifaces.find(associationPath.str);
+ if (findEndpoint == associationMaps.ifaces.end())
+ {
+ return {};
+ }
+ const std::vector<std::string>& association =
+ std::get<endpointsPos>(findEndpoint->second);
+ std::unordered_set<std::string> associationSet(association.begin(),
+ association.end());
+ const std::vector<InterfaceMapType::value_type>& interfacePairs =
+ getSubTree(interfaceMap, reqPath, depth, interfaces);
+
+ std::vector<InterfaceMapType::value_type> output;
+ for (const InterfaceMapType::value_type& interfacePair : interfacePairs)
+ {
+ if (associationSet.contains(interfacePair.first))
+ {
+ output.emplace_back(interfacePair);
+ }
+ }
+ return output;
+}
+
+std::vector<std::string> getAssociatedSubTreePaths(
+ const InterfaceMapType& interfaceMap,
+ const AssociationMaps& associationMaps,
+ const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath, int32_t depth,
+ std::vector<std::string>& interfaces)
+{
+ auto findEndpoint = associationMaps.ifaces.find(associationPath.str);
+ if (findEndpoint == associationMaps.ifaces.end())
+ {
+ return {};
+ }
+ const std::vector<std::string>& association =
+ std::get<endpointsPos>(findEndpoint->second);
+ std::unordered_set<std::string> associationSet(association.begin(),
+ association.end());
+ const std::vector<std::string>& paths =
+ getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
+
+ std::vector<std::string> output;
+ for (const auto& path : paths)
+ {
+ if (associationSet.contains(path))
+ {
+ output.emplace_back(path);
+ }
+ }
+ return output;
+}
\ No newline at end of file
diff --git a/src/handler.hpp b/src/handler.hpp
index 74cb234..9e692e5 100644
--- a/src/handler.hpp
+++ b/src/handler.hpp
@@ -24,3 +24,47 @@
std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
std::string reqPath, int32_t depth,
std::vector<std::string>& interfaces);
+
+/**
+ * @brief Get the Associated Sub Tree object
+ *
+ * @param interfaceMap Mapper Structure storing all associations
+ * @param associationMaps Map of association between objects
+ * @param associationPath Object path to get the endpoint from
+ * @param reqPath Base path to search for the subtree
+ * @param depth Level of depth to search into the base path
+ * @param interfaces Interface filter
+ *
+ * Use getSubTree and return only the dbus objects that are in the endpoint
+ * of associationPath.
+ *
+ * @return std::vector<InterfaceMapType::value_type>
+ */
+std::vector<InterfaceMapType::value_type>
+ getAssociatedSubTree(const InterfaceMapType& interfaceMap,
+ const AssociationMaps& associationMaps,
+ const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath,
+ int32_t depth, std::vector<std::string>& interfaces);
+
+/**
+ * @brief Get the Associated Sub Tree Paths object
+ *
+ * @param interfaceMap Mapper Structure storing all associations
+ * @param associationMaps Map of association between objects
+ * @param associationPath Object path to get the endpoint from
+ * @param reqPath Base path to search for the subtree
+ * @param depth Level of depth to search into the base path
+ * @param interfaces Interface filter
+ *
+ * Use getSubTreePaths and return only the dbus objects that are in the
+ * endpoint of associationPath.
+ *
+ * @return std::vector<std::string>
+ */
+std::vector<std::string> getAssociatedSubTreePaths(
+ const InterfaceMapType& interfaceMap,
+ const AssociationMaps& associationMaps,
+ const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath, int32_t depth,
+ std::vector<std::string>& interfaces);
diff --git a/src/main.cpp b/src/main.cpp
index 5f43767..568f460 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -596,6 +596,26 @@
return getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
});
+ iface->register_method(
+ "GetAssociatedSubTree",
+ [&interfaceMap](const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath,
+ int32_t depth, std::vector<std::string>& interfaces) {
+ return getAssociatedSubTree(interfaceMap, associationMaps,
+ associationPath, reqPath, depth,
+ interfaces);
+ });
+
+ iface->register_method(
+ "GetAssociatedSubTreePaths",
+ [&interfaceMap](const sdbusplus::message::object_path& associationPath,
+ const sdbusplus::message::object_path& reqPath,
+ int32_t depth, std::vector<std::string>& interfaces) {
+ return getAssociatedSubTreePaths(interfaceMap, associationMaps,
+ associationPath, reqPath, depth,
+ interfaces);
+ });
+
iface->initialize();
io.post([&]() {
diff --git a/src/test/handler.cpp b/src/test/handler.cpp
index 693ab21..b7dfbb3 100644
--- a/src/test/handler.cpp
+++ b/src/test/handler.cpp
@@ -33,6 +33,33 @@
"/test/object_path_0/child/grandchild/dog",
{{"test_object_connection_3", {"test_interface_3"}}},
}};
+
+ AssociationMaps associationMap = {
+ .ifaces =
+ {
+ {
+ "/test/object_path_0/descendent",
+ {
+ std::shared_ptr<sdbusplus::asio::dbus_interface>(),
+ {
+ "/test/object_path_0/child",
+ "/test/object_path_0/child/grandchild",
+ },
+ },
+ },
+ {
+ "/test/object_path_0/child/descendent",
+ {
+ std::shared_ptr<sdbusplus::asio::dbus_interface>(),
+ {
+ "/test/object_path_0/child/grandchild",
+ },
+ },
+ },
+ },
+ .owners = {},
+ .pending = {},
+ };
};
TEST_F(TestHandler, AddObjectMapResult)
@@ -257,3 +284,130 @@
ASSERT_THAT(subtreePath,
ElementsAre("/test/object_path_0/child/grandchild/dog"));
}
+
+TEST_F(TestHandler, getAssociatedSubTreeBad)
+{
+ sdbusplus::message::object_path path("/test/object_path_0");
+ sdbusplus::message::object_path validAssociatedPath = path / "descendent";
+ std::vector<std::string> invalidInterfaces = {"test_interface_3"};
+ std::vector<std::string> validInterfaces = {"test_interface_1",
+ "test_interface_2"};
+ // Associated path, but invalid interface
+ ASSERT_TRUE(getAssociatedSubTree(interfaceMap, associationMap,
+ validAssociatedPath, path, 0,
+ invalidInterfaces)
+ .empty());
+
+ // Valid interface, not associated
+ ASSERT_TRUE(getAssociatedSubTree(interfaceMap, associationMap, path / "dog",
+ path, 0, validInterfaces)
+ .empty());
+
+ // Invalid path, with valid association
+ path = sdbusplus::message::object_path("/invalid_path");
+ EXPECT_THROW(
+ getAssociatedSubTree(interfaceMap, associationMap, validAssociatedPath,
+ path, 0, validInterfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAssociatedSubTreeGood)
+{
+ sdbusplus::message::object_path path0("/test/object_path_0");
+ sdbusplus::message::object_path path1("/test/object_path_0/child");
+ sdbusplus::message::object_path associatedPath = path0 / "descendent";
+ std::vector<std::string> interfaces = {"test_interface_1",
+ "test_interface_2",
+ // Not associated to path
+ "test_interface_3"};
+
+ // Path0
+ std::vector<InterfaceMapType::value_type> subtree = getAssociatedSubTree(
+ interfaceMap, associationMap, associatedPath, path0, 0, interfaces);
+ ASSERT_EQ(subtree.size(), 2);
+ ConnectionNames connection = subtree[0].second;
+ auto object = connection.find("test_object_connection_1");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+ connection = subtree[1].second;
+ object = connection.find("test_object_connection_2");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_2"));
+
+ // Path0 with Depth path of 1
+ subtree = getAssociatedSubTree(interfaceMap, associationMap, associatedPath,
+ path0, 1, interfaces);
+ ASSERT_EQ(subtree.size(), 1);
+ connection = subtree[0].second;
+ object = connection.find("test_object_connection_1");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+ // Path1
+ subtree = getAssociatedSubTree(interfaceMap, associationMap,
+ path1 / "descendent", path1, 0, interfaces);
+ ASSERT_EQ(subtree.size(), 1);
+ connection = subtree[0].second;
+ object = connection.find("test_object_connection_2");
+ ASSERT_NE(object, connection.end());
+ ASSERT_THAT(object->second, ElementsAre("test_interface_2"));
+}
+
+TEST_F(TestHandler, getAssociatedSubTreePathsBad)
+{
+ sdbusplus::message::object_path path("/test/object_path_0");
+ sdbusplus::message::object_path validAssociatedPath = path / "descendent";
+ std::vector<std::string> invalidInterfaces = {"test_interface_3"};
+ std::vector<std::string> validInterfaces = {"test_interface_1",
+ "test_interface_2"};
+ // Associated path, but invalid interface
+ ASSERT_TRUE(getAssociatedSubTreePaths(interfaceMap, associationMap,
+ validAssociatedPath, path, 0,
+ invalidInterfaces)
+ .empty());
+
+ // Valid interface, not associated
+ ASSERT_TRUE(getAssociatedSubTreePaths(interfaceMap, associationMap,
+ path / "dog", path, 0,
+ validInterfaces)
+ .empty());
+
+ // Invalid path, with valid association
+ path = sdbusplus::message::object_path("/invalid_path");
+ EXPECT_THROW(
+ getAssociatedSubTreePaths(interfaceMap, associationMap,
+ validAssociatedPath, path, 0,
+ validInterfaces),
+ sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAssociatedSubTreePathsGood)
+{
+ sdbusplus::message::object_path path0("/test/object_path_0");
+ sdbusplus::message::object_path path1("/test/object_path_0/child");
+ sdbusplus::message::object_path associatedPath = path0 / "descendent";
+ std::vector<std::string> interfaces = {"test_interface_1",
+ "test_interface_2",
+ // Not associated to path
+ "test_interface_3"};
+
+ // Path0
+ std::vector<std::string> subtreePath = getAssociatedSubTreePaths(
+ interfaceMap, associationMap, associatedPath, path0, 0, interfaces);
+ ASSERT_THAT(subtreePath,
+ ElementsAre("/test/object_path_0/child",
+ "/test/object_path_0/child/grandchild"));
+
+ // Path0 with Depth path of 1
+ subtreePath = getAssociatedSubTreePaths(
+ interfaceMap, associationMap, associatedPath, path0, 1, interfaces);
+ ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child"));
+
+ // Path1
+ subtreePath =
+ getAssociatedSubTreePaths(interfaceMap, associationMap,
+ path1 / "descendent", path1, 0, interfaces);
+ ASSERT_THAT(subtreePath,
+ ElementsAre("/test/object_path_0/child/grandchild"));
+}