summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad Bishop <bradleyb@fuzziesquirrel.com>2017-05-23 18:06:38 -0400
committerBrad Bishop <bradleyb@fuzziesquirrel.com>2017-06-19 16:21:47 -0400
commit4b916f139a9b3ccac76610f5e4da1fe0bb4dfd51 (patch)
treedab7d7442a70591a533930dff1102de7ff4d6151
parentfac1b1035ad5a0339fc1af600866343452dd77d3 (diff)
downloadphosphor-dbus-monitor-4b916f139a9b3ccac76610f5e4da1fe0bb4dfd51.tar.gz
phosphor-dbus-monitor-4b916f139a9b3ccac76610f5e4da1fe0bb4dfd51.zip
Add property watches
Property watches cache DBus property values given an externally supplied index of property names and paths, in an externally supplied storage location. Change-Id: I155081da88c3ab0e4f6a13b012fc9719203b1888 Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
-rw-r--r--src/Makefile.am3
-rw-r--r--src/data_types.hpp11
-rw-r--r--src/example/example.yaml9
-rwxr-xr-xsrc/pdmgen.py131
-rw-r--r--src/propertywatch.cpp49
-rw-r--r--src/propertywatch.hpp167
-rw-r--r--src/propertywatchimpl.hpp183
-rw-r--r--src/templates/generated.mako.hpp66
-rw-r--r--src/watch.hpp36
9 files changed, 654 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 0dfe63b..3b4d9cf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,7 +8,8 @@ sbin_PROGRAMS = phosphor-dbus-monitor
phosphor_dbus_monitor_SOURCES = \
functor.cpp \
main.cpp \
- monitor.cpp
+ monitor.cpp \
+ propertywatch.cpp
phosphor_dbus_monitor_LDADD = \
$(SDBUSPLUS_LIBS) \
$(PHOSPHOR_LOGGING_LIBS)
diff --git a/src/data_types.hpp b/src/data_types.hpp
index 653b7c3..af0ef68 100644
--- a/src/data_types.hpp
+++ b/src/data_types.hpp
@@ -69,6 +69,17 @@ using PropertiesChanged = std::map <
std::string,
sdbusplus::message::variant<T >>;
+/** @brief Lookup index for properties . */
+// *INDENT-OFF*
+using PropertyIndex = TupleRefMap <
+ TupleOfRefs<
+ const std::string,
+ const std::string,
+ any_ns::any>,
+ const std::string,
+ const std::string,
+ const std::string >;
+// *INDENT-ON*
} // namespace monitoring
} // namespace dbus
} // namespace phosphor
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 3d60396..4e216cd 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -44,3 +44,12 @@
- interface: xyz.openbmc_project.Sensor.Value
meta: property
property: ValueB
+
+- name: example property watch
+ description: >
+ 'A property watch instructs PDM to maintain a cache of the state
+ of the specified properties on the specified DBus objects.'
+ class: watch
+ watch: property
+ paths: example path group
+ properties: example property group
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 61546eb..070c118 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -208,6 +208,29 @@ class Property(ConfigEntry):
super(Property, self).setup(objs)
+class Instance(ConfigEntry):
+ '''Property/Path association.'''
+
+ def __init__(self, *a, **kw):
+ super(Instance, self).__init__(**kw)
+
+ def setup(self, objs):
+ '''Resolve elements to indicies.'''
+
+ self.interface = get_index(
+ objs, 'interface', self.name['property']['interface'])
+ self.prop = get_index(
+ objs, 'propertyname', self.name['property']['property'])
+ self.propmeta = get_index(
+ objs, 'meta', self.name['property']['meta'])
+ self.path = get_index(
+ objs, 'pathname', self.name['path']['path'])
+ self.pathmeta = get_index(
+ objs, 'meta', self.name['path']['meta'])
+
+ super(Instance, self).setup(objs)
+
+
class Group(ConfigEntry):
'''Pop the members keyword for groups.'''
@@ -294,6 +317,102 @@ class GroupOfProperties(ImplicitGroup):
super(GroupOfProperties, self).setup(objs)
+class GroupOfInstances(ImplicitGroup):
+ '''A group of property instances.'''
+
+ def __init__(self, *a, **kw):
+ super(GroupOfInstances, self).__init__(**kw)
+
+ def setup(self, objs):
+ '''Resolve group members.'''
+
+ def map_member(x):
+ path = get_index(objs, 'pathname', x['path']['path'])
+ pathmeta = get_index(objs, 'meta', x['path']['meta'])
+ interface = get_index(
+ objs, 'interface', x['property']['interface'])
+ prop = get_index(objs, 'propertyname', x['property']['property'])
+ propmeta = get_index(objs, 'meta', x['property']['meta'])
+ instance = get_index(objs, 'instance', x)
+
+ return (path, pathmeta, interface, prop, propmeta, instance)
+
+ self.members = map(
+ map_member,
+ self.members)
+
+ super(GroupOfInstances, self).setup(objs)
+
+
+class HasPropertyIndex(ConfigEntry):
+ '''Handle config file directives that require an index to be
+ constructed.'''
+
+ def __init__(self, *a, **kw):
+ self.paths = kw.pop('paths')
+ self.properties = kw.pop('properties')
+ super(HasPropertyIndex, self).__init__(**kw)
+
+ def factory(self, objs):
+ '''Create a group of instances for this index.'''
+
+ members = []
+ path_group = get_index(
+ objs, 'pathgroup', self.paths, config=self.configfile)
+ property_group = get_index(
+ objs, 'propertygroup', self.properties, config=self.configfile)
+
+ for path in objs['pathgroup'][path_group].members:
+ for prop in objs['propertygroup'][property_group].members:
+ member = {
+ 'path': path,
+ 'property': prop,
+ }
+ members.append(member)
+
+ args = {
+ 'members': members,
+ 'class': 'instancegroup',
+ 'instancegroup': 'instance',
+ 'name': '{0} {1}'.format(self.paths, self.properties)
+ }
+
+ group = GroupOfInstances(configfile=self.configfile, **args)
+ add_unique(group, objs, config=self.configfile)
+ group.factory(objs)
+
+ super(HasPropertyIndex, self).factory(objs)
+
+ def setup(self, objs):
+ '''Resolve path, property, and instance groups.'''
+
+ self.instances = get_index(
+ objs,
+ 'instancegroup',
+ '{0} {1}'.format(self.paths, self.properties),
+ config=self.configfile)
+ self.paths = get_index(
+ objs,
+ 'pathgroup',
+ self.paths,
+ config=self.configfile)
+ self.properties = get_index(
+ objs,
+ 'propertygroup',
+ self.properties,
+ config=self.configfile)
+ self.datatype = objs['propertygroup'][self.properties].datatype
+
+ super(HasPropertyIndex, self).setup(objs)
+
+
+class PropertyWatch(HasPropertyIndex):
+ '''Handle the property watch config file directive.'''
+
+ def __init__(self, *a, **kw):
+ super(PropertyWatch, self).__init__(**kw)
+
+
class Everything(Renderer):
'''Parse/render entry point.'''
@@ -315,6 +434,12 @@ class Everything(Renderer):
'property': {
'element': Property,
},
+ 'watch': {
+ 'property': PropertyWatch,
+ },
+ 'instance': {
+ 'element': Instance,
+ },
}
if cls not in class_map:
@@ -401,6 +526,9 @@ class Everything(Renderer):
self.properties = kw.pop('property', [])
self.propertynames = kw.pop('propertyname', [])
self.propertygroups = kw.pop('propertygroup', [])
+ self.instances = kw.pop('instance', [])
+ self.instancegroups = kw.pop('instancegroup', [])
+ self.watches = kw.pop('watch', [])
super(Everything, self).__init__(**kw)
@@ -419,6 +547,9 @@ class Everything(Renderer):
pathmeta=self.pathmeta,
pathgroups=self.pathgroups,
propertygroups=self.propertygroups,
+ instances=self.instances,
+ watches=self.watches,
+ instancegroups=self.instancegroups,
indent=Indent()))
if __name__ == '__main__':
diff --git a/src/propertywatch.cpp b/src/propertywatch.cpp
new file mode 100644
index 0000000..defde78
--- /dev/null
+++ b/src/propertywatch.cpp
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2017 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "propertywatchimpl.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @brief convert index.
+ *
+ * An optimal start implementation requires objects organized in the
+ * same structure as the mapper response. The convert method reorganizes
+ * the flat structure of the index to match.
+ *
+ * @param[in] index - The index to be converted.
+ */
+MappedPropertyIndex convert(const PropertyIndex& index)
+{
+ MappedPropertyIndex m;
+
+ for (const auto& i : index)
+ {
+ const auto& path = std::get<0>(i.first);
+ const auto& interface = std::get<1>(i.first);
+ const auto& property = std::get<2>(i.first);
+ m[path][interface].push_back(property);
+ }
+
+ return m;
+}
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/propertywatch.hpp b/src/propertywatch.hpp
new file mode 100644
index 0000000..59db599
--- /dev/null
+++ b/src/propertywatch.hpp
@@ -0,0 +1,167 @@
+/**
+ * @file propertywatch.hpp
+ * @brief PropertyWatch class declarations.
+ *
+ * In general class users should include propertywatchimpl.hpp instead to avoid
+ * link failures.
+ */
+#pragma once
+
+#include "data_types.hpp"
+#include "watch.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class PropertyWatch
+ * @brief Type agnostic, factored out logic for property watches.
+ *
+ * A property watch maintains the state of one or more DBus properties
+ * as specified by the supplied index.
+ */
+template <typename DBusInterfaceType>
+class PropertyWatch : public Watch
+{
+ public:
+ PropertyWatch() = delete;
+ PropertyWatch(const PropertyWatch&) = delete;
+ PropertyWatch(PropertyWatch&&) = default;
+ PropertyWatch& operator=(const PropertyWatch&) = delete;
+ PropertyWatch& operator=(PropertyWatch&&) = default;
+ virtual ~PropertyWatch() = default;
+ explicit PropertyWatch(const PropertyIndex& watchIndex)
+ : Watch(), index(watchIndex), alreadyRan(false) {}
+
+ /** @brief Start the watch.
+ *
+ * Watch start interface implementation for PropertyWatch.
+ */
+ void start() override;
+
+ /** @brief Update properties.
+ *
+ * Subclasses to query the properties specified by the index
+ * and update the cache.
+ *
+ * @param[in] busName - The busname hosting the interface to query.
+ * @param[in] path - The path of the interface to query.
+ * @param[in] interface - The interface to query.
+ */
+ virtual void updateProperties(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface) = 0;
+
+ /** @brief Dbus signal callback for PropertiesChanged.
+ *
+ * Subclasses to update the cache.
+ *
+ * @param[in] message - The org.freedesktop.DBus.PropertiesChanged
+ * message.
+ * @param[in] path - The path associated with the message.
+ * @param[in] interface - The interface associated with the message.
+ */
+ virtual void propertiesChanged(
+ sdbusplus::message::message&,
+ const std::string& path,
+ const std::string& interface) = 0;
+
+ /** @brief Dbus signal callback for InterfacesAdded.
+ *
+ * Subclasses to update the cache.
+ *
+ * @param[in] msg - The org.freedesktop.DBus.PropertiesChanged
+ * message.
+ */
+ virtual void interfacesAdded(sdbusplus::message::message& msg) = 0;
+
+ protected:
+
+ /** @brief Property names and their associated storage. */
+ const PropertyIndex& index;
+
+ private:
+
+ /** @brief The start method should only be invoked once. */
+ bool alreadyRan;
+};
+
+/** @class PropertyWatchOfType
+ * @brief Type specific logic for PropertyWatch.
+ *
+ * @tparam DBusInterfaceType - DBus access delegate.
+ * @tparam T - The type of the properties being watched.
+ */
+template <typename T, typename DBusInterfaceType>
+class PropertyWatchOfType : public PropertyWatch<DBusInterfaceType>
+{
+ public:
+ PropertyWatchOfType() = default;
+ PropertyWatchOfType(const PropertyWatchOfType&) = delete;
+ PropertyWatchOfType(PropertyWatchOfType&&) = default;
+ PropertyWatchOfType& operator=(const PropertyWatchOfType&) = delete;
+ PropertyWatchOfType& operator=(PropertyWatchOfType&&) = default;
+ ~PropertyWatchOfType() = default;
+ explicit PropertyWatchOfType(
+ const PropertyIndex& watchIndex) :
+ PropertyWatch<DBusInterfaceType>(watchIndex) {}
+
+ /** @brief PropertyMatch implementation for PropertyWatchOfType.
+ *
+ * @param[in] busName - The busname hosting the interface to query.
+ * @param[in] path - The path of the interface to query.
+ * @param[in] interface - The interface to query.
+ */
+ void updateProperties(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface) override;
+
+ /** @brief PropertyMatch implementation for PropertyWatchOfType.
+ *
+ * @param[in] msg - The org.freedesktop.DBus.PropertiesChanged
+ * message.
+ * @param[in] path - The path associated with the message.
+ * @param[in] interface - The interface associated with the message.
+ */
+ void propertiesChanged(
+ sdbusplus::message::message& msg,
+ const std::string& path,
+ const std::string& interface) override;
+
+ /** @brief DBus agnostic implementation of interfacesAdded.
+ *
+ * @param[in] path - The path of the properties that changed.
+ * @param[in] interface - The interface of the properties that
+ * changed.
+ * @param[in] properites - The properties that changed.
+ */
+ void propertiesChanged(
+ const std::string& path,
+ const std::string& interface,
+ const PropertiesChanged<T>& properties);
+
+ /** @brief PropertyMatch implementation for PropertyWatchOfType.
+ *
+ * @param[in] msg - The org.freedesktop.DBus.PropertiesChanged
+ * message.
+ */
+ void interfacesAdded(sdbusplus::message::message& msg) override;
+
+ /** @brief DBus agnostic implementation of interfacesAdded.
+ *
+ * @param[in] path - The path of the added interfaces.
+ * @param[in] interfaces - The added interfaces.
+ */
+ void interfacesAdded(
+ const std::string& path,
+ const InterfacesAdded<T>& interfaces);
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/propertywatchimpl.hpp b/src/propertywatchimpl.hpp
new file mode 100644
index 0000000..46799ed
--- /dev/null
+++ b/src/propertywatchimpl.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <vector>
+#include "data_types.hpp"
+#include "propertywatch.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+static constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+static constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+static constexpr auto MAPPER_INTERFACE =
+ "xyz.openbmc_project.ObjectMapper";
+
+using MappedPropertyIndex =
+ RefKeyMap<const std::string,
+ RefKeyMap<const std::string,
+ RefVector<const std::string>>>;
+
+MappedPropertyIndex convert(const PropertyIndex& index);
+
+template <typename DBusInterfaceType>
+void PropertyWatch<DBusInterfaceType>::start()
+{
+ if (alreadyRan)
+ {
+ return;
+ }
+ else
+ {
+ alreadyRan = true;
+ }
+
+ // The index has a flat layout which is not optimal here. Nest
+ // properties in a map of interface names in a map of object paths.
+ auto mapped = convert(index);
+
+ for (const auto& m : mapped)
+ {
+ const auto& path = m.first.get();
+ const auto& interfaces = m.second;
+
+ // Watch for new interfaces on this path.
+ DBusInterfaceType::addMatch(
+ sdbusplus::bus::match::rules::interfacesAdded(path),
+ [this](auto & msg)
+ // *INDENT-OFF*
+ {
+ this->interfacesAdded(msg);
+ });
+ // *INDENT-ON*
+
+ // Do a query to populate the cache. Start with a mapper query.
+ // The specific services are queried below.
+ const std::vector<std::string> queryInterfaces; // all interfaces
+ auto mapperResp =
+ DBusInterfaceType::template callMethodAndRead<GetObject>(
+ MAPPER_BUSNAME,
+ MAPPER_PATH,
+ MAPPER_INTERFACE,
+ "GetObject",
+ path,
+ queryInterfaces);
+
+ for (const auto& i : interfaces)
+ {
+ const auto& interface = i.first.get();
+
+ // Watch for property changes on this interface.
+ DBusInterfaceType::addMatch(
+ sdbusplus::bus::match::rules::propertiesChanged(
+ path, interface),
+ [this](auto & msg)
+ // *INDENT-OFF*
+ {
+ std::string interface;
+ msg.read(interface);
+ auto path = msg.get_path();
+ this->propertiesChanged(msg, path, interface);
+ });
+ // *INDENT-ON*
+
+ // The mapper response is a busname:[interfaces] map. Look for
+ // each interface in the index and if found, query the service and
+ // populate the cache entries for the interface.
+ for (const auto& mr : mapperResp)
+ {
+ const auto& busName = mr.first;
+ const auto& mapperInterfaces = mr.second;
+ if (mapperInterfaces.end() == std::find(
+ mapperInterfaces.begin(),
+ mapperInterfaces.end(),
+ interface))
+ {
+ // This interface isn't being watched.
+ continue;
+ }
+
+ // Delegate type specific property updates to subclasses.
+ updateProperties(busName, path, interface);
+ }
+ }
+ }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::updateProperties(
+ const std::string& busName,
+ const std::string& path,
+ const std::string& interface)
+{
+ auto properties =
+ DBusInterfaceType::template callMethodAndRead<PropertiesChanged<T>>(
+ busName.c_str(),
+ path.c_str(),
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ interface);
+ propertiesChanged(path, interface, properties);
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
+ const std::string& path,
+ const std::string& interface,
+ const PropertiesChanged<T>& properties)
+{
+ // Update the cache for any watched properties.
+ for (const auto& p : properties)
+ {
+ auto key = std::make_tuple(path, interface, p.first);
+ auto item = this->index.find(key);
+ if (item == this->index.end())
+ {
+ // This property isn't being watched.
+ continue;
+ }
+
+ std::get<2>(item->second).get() = p.second.template get<T>();
+ }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
+ sdbusplus::message::message& msg,
+ const std::string& path,
+ const std::string& interface)
+{
+ PropertiesChanged<T> properties;
+ msg.read(properties);
+ propertiesChanged(path, interface, properties);
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
+ const std::string& path,
+ const InterfacesAdded<T>& interfaces)
+{
+ for (const auto& i : interfaces)
+ {
+ propertiesChanged(path, i.first, i.second);
+ }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
+ sdbusplus::message::message& msg)
+{
+ sdbusplus::message::object_path path;
+ InterfacesAdded<T> interfaces;
+ msg.read(path, interfaces);
+ interfacesAdded(path, interfaces);
+}
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 7c6e527..0911e3a 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -4,6 +4,9 @@
#include <array>
#include <string>
+#include "data_types.hpp"
+#include "propertywatchimpl.hpp"
+#include "sdbusplus.hpp"
using namespace std::string_literals;
@@ -77,6 +80,69 @@ struct ConfigProperties
return properties;
}
};
+
+struct ConfigPropertyStorage
+{
+ using Storage = std::array<any_ns::any, ${len(instances)}>;
+
+ static auto& get()
+ {
+ static Storage storage;
+ return storage;
+ }
+};
+
+struct ConfigPropertyIndicies
+{
+ using PropertyIndicies = std::array<PropertyIndex, ${len(instancegroups)}>;
+
+ static auto& get()
+ {
+ static const PropertyIndicies propertyIndicies =
+ {
+ {
+% for g in instancegroups:
+ {
+ % for i in g.members:
+ {
+ PropertyIndex::key_type
+ {
+ ConfigPaths::get()[${i[0]}],
+ ConfigInterfaces::get()[${i[2]}],
+ ConfigProperties::get()[${i[3]}]
+ },
+ PropertyIndex::mapped_type
+ {
+ ConfigMeta::get()[${i[1]}],
+ ConfigMeta::get()[${i[4]}],
+ ConfigPropertyStorage::get()[${i[5]}]
+ },
+ },
+ % endfor
+ },
+% endfor
+ }
+ };
+ return propertyIndicies;
+ }
+};
+
+struct ConfigPropertyWatches
+{
+ using PropertyWatches = std::array<std::unique_ptr<Watch>, ${len(watches)}>;
+
+ static auto& get()
+ {
+ static const PropertyWatches propertyWatches =
+ {
+% for w in watches:
+ std::make_unique<PropertyWatchOfType<${w.datatype}, SDBusPlus>>(
+ ConfigPropertyIndicies::get()[${w.instances}]),
+% endfor
+ };
+ return propertyWatches;
+ }
+};
} // namespace monitoring
} // namespace dbus
} // namespace phosphor
diff --git a/src/watch.hpp b/src/watch.hpp
new file mode 100644
index 0000000..1ba671d
--- /dev/null
+++ b/src/watch.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class Watch
+ * @brief Watch interface.
+ *
+ * The start method is invoked by main() on all watches of any type
+ * at application startup, to allow watches to perform custom setup
+ * or initialization. Typical implementations might register dbus
+ * callbacks or perform queries.
+ *
+ * Watches of any type can be started.
+ */
+class Watch
+{
+ public:
+ Watch() = default;
+ Watch(const Watch&) = default;
+ Watch(Watch&&) = default;
+ Watch& operator=(const Watch&) = default;
+ Watch& operator=(Watch&&) = default;
+ virtual ~Watch() = default;
+
+ /** @brief Start the watch. */
+ virtual void start() = 0;
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
OpenPOWER on IntegriCloud