From 4b916f139a9b3ccac76610f5e4da1fe0bb4dfd51 Mon Sep 17 00:00:00 2001 From: Brad Bishop Date: Tue, 23 May 2017 18:06:38 -0400 Subject: 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 --- src/Makefile.am | 3 +- src/data_types.hpp | 11 +++ src/example/example.yaml | 9 ++ src/pdmgen.py | 131 ++++++++++++++++++++++++++++ src/propertywatch.cpp | 49 +++++++++++ src/propertywatch.hpp | 167 +++++++++++++++++++++++++++++++++++ src/propertywatchimpl.hpp | 183 +++++++++++++++++++++++++++++++++++++++ src/templates/generated.mako.hpp | 66 ++++++++++++++ src/watch.hpp | 36 ++++++++ 9 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 src/propertywatch.cpp create mode 100644 src/propertywatch.hpp create mode 100644 src/propertywatchimpl.hpp create mode 100644 src/watch.hpp 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>; +/** @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 +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 +class PropertyWatchOfType : public PropertyWatch +{ + 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(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& 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& 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 +#include +#include +#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>>; + +MappedPropertyIndex convert(const PropertyIndex& index); + +template +void PropertyWatch::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 queryInterfaces; // all interfaces + auto mapperResp = + DBusInterfaceType::template callMethodAndRead( + 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 +void PropertyWatchOfType::updateProperties( + const std::string& busName, + const std::string& path, + const std::string& interface) +{ + auto properties = + DBusInterfaceType::template callMethodAndRead>( + busName.c_str(), + path.c_str(), + "org.freedesktop.DBus.Properties", + "GetAll", + interface); + propertiesChanged(path, interface, properties); +} + +template +void PropertyWatchOfType::propertiesChanged( + const std::string& path, + const std::string& interface, + const PropertiesChanged& 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(); + } +} + +template +void PropertyWatchOfType::propertiesChanged( + sdbusplus::message::message& msg, + const std::string& path, + const std::string& interface) +{ + PropertiesChanged properties; + msg.read(properties); + propertiesChanged(path, interface, properties); +} + +template +void PropertyWatchOfType::interfacesAdded( + const std::string& path, + const InterfacesAdded& interfaces) +{ + for (const auto& i : interfaces) + { + propertiesChanged(path, i.first, i.second); + } +} + +template +void PropertyWatchOfType::interfacesAdded( + sdbusplus::message::message& msg) +{ + sdbusplus::message::object_path path; + InterfacesAdded 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 #include +#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; + + static auto& get() + { + static Storage storage; + return storage; + } +}; + +struct ConfigPropertyIndicies +{ + using PropertyIndicies = std::array; + + 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, ${len(watches)}>; + + static auto& get() + { + static const PropertyWatches propertyWatches = + { +% for w in watches: + std::make_unique>( + 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 -- cgit v1.2.1