Method support

Add support for a method callback.  The method callback enables
arbitrary DBus method calls.  A sample use case could be
starting a systemd unit via the sytemd DBus API.

Change-Id: If25131d11497c82f862ae1f47da066c5fd8b2e2e
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 4d6658d..0380f6c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -26,7 +26,8 @@
 	templates/conditional.mako.cpp \
 	templates/count.mako.cpp \
 	templates/generated.mako.hpp \
-	templates/journal.mako.cpp
+	templates/journal.mako.cpp \
+	templates/method.mako.cpp
 
 generated.hpp: $(PDMGEN) $(YAML_PATH) $(TEMPLATES)
 	$(AM_V_GEN)$(PYTHON) ${PDMGEN} \
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 2066227..d76e3ad 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -78,6 +78,21 @@
   severity: INFO
   message: Hello world from PDM!
 
+- name: example method callback
+  description: >
+    'The method callback invokes the specified DBus method.'
+  class: callback
+  callback: method
+  service: org.freedesktop.systemd1
+  path: /org/freedesktop/systemd1
+  interface: org.freedesktop.systemd1.Manager
+  method: StartUnit
+  args:
+    - value: foo.unit
+      type: string
+    - value: replace
+      type: string
+
 - name: example callback group
   description: >
     'Callbacks groups are simply named collections of other callbacks.
diff --git a/src/method.hpp b/src/method.hpp
new file mode 100644
index 0000000..66c19db
--- /dev/null
+++ b/src/method.hpp
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <experimental/tuple>
+#include "callback.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+namespace detail
+{
+
+/** @class CallDBusMethod
+ *  @brief Provide explicit call forwarding to
+ *     DBusInterface::callMethodNoReply.
+ *
+ *  @tparam DBusInterface - The DBus interface to use.
+ *  @tparam MethodArgs - DBus method argument types.
+ */
+template <typename DBusInterface, typename ...MethodArgs>
+struct CallDBusMethod
+{
+    static void op(
+        const std::string& bus,
+        const std::string& path,
+        const std::string& iface,
+        const std::string& method,
+        MethodArgs&& ...args)
+    {
+        DBusInterface::callMethodNoReply(
+            bus,
+            path,
+            iface,
+            method,
+            std::forward<MethodArgs>(args)...);
+    }
+};
+} // namespace detail
+
+/** @class MethodBase
+ *  @brief Invoke DBus method callback implementation.
+ *
+ *  The method callback invokes the client supplied DBus method.
+ */
+class MethodBase : public Callback
+{
+    public:
+        MethodBase() = delete;
+        MethodBase(const MethodBase&) = delete;
+        MethodBase(MethodBase&&) = default;
+        MethodBase& operator=(const MethodBase&) = delete;
+        MethodBase& operator=(MethodBase&&) = default;
+        virtual ~MethodBase() = default;
+        MethodBase(
+            const std::string& b,
+            const std::string& p,
+            const std::string& i,
+            const std::string& m)
+            : Callback(),
+              bus(b),
+              path(p),
+              interface(i),
+                  method(m) {}
+
+        /** @brief Callback interface implementation. */
+        void operator()() override = 0;
+
+    protected:
+        const std::string& bus;
+        const std::string& path;
+        const std::string& interface;
+        const std::string& method;
+};
+
+/** @class Method
+ *  @brief C++ type specific logic for the method callback.
+ *
+ *  @tparam DBusInterface - The DBus interface to use to call the method.
+ *  @tparam MethodArgs - DBus method argument types.
+ */
+template <typename DBusInterface, typename ...MethodArgs>
+class Method : public MethodBase
+{
+    public:
+        Method() = delete;
+        Method(const Method&) = default;
+        Method(Method&&) = default;
+        Method& operator=(const Method&) = default;
+        Method& operator=(Method&&) = default;
+        ~Method() = default;
+        Method(
+            const std::string& bus,
+            const std::string& path,
+            const std::string& iface,
+            const std::string& method,
+            MethodArgs&& ... arguments)
+            : MethodBase(bus, path, iface, method),
+              args(std::forward<MethodArgs>(arguments)...) {}
+
+        /** @brief Callback interface implementation. */
+        void operator()() override
+        {
+            std::experimental::apply(
+                detail::CallDBusMethod<DBusInterface, MethodArgs...>::op,
+                std::tuple_cat(
+                    std::make_tuple(bus),
+                    std::make_tuple(path),
+                    std::make_tuple(interface),
+                    std::make_tuple(method),
+                    args));
+        }
+
+    private:
+        std::tuple<MethodArgs...> args;
+};
+
+/** @brief Argument type deduction for constructing Method instances. */
+template <typename DBusInterface, typename ...MethodArgs>
+auto makeMethod(
+    const std::string& bus,
+    const std::string& path,
+    const std::string& iface,
+    const std::string& method,
+    MethodArgs&& ... arguments)
+{
+    return std::make_unique<Method<DBusInterface, MethodArgs...>>(
+               bus,
+               path,
+               iface,
+               method,
+               std::forward<MethodArgs>(arguments)...);
+}
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 6dc6861..5d4bf29 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -635,6 +635,85 @@
             indent=indent)
 
 
+class Method(ConfigEntry, Renderer):
+    '''Handle the method callback config file directive.'''
+
+    def __init__(self, *a, **kw):
+        self.service = kw.pop('service')
+        self.path = kw.pop('path')
+        self.interface = kw.pop('interface')
+        self.method = kw.pop('method')
+        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
+        super(Method, self).__init__(**kw)
+
+    def factory(self, objs):
+        args = {
+            'class': 'interface',
+            'interface': 'element',
+            'name': self.service
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        args = {
+            'class': 'pathname',
+            'pathname': 'element',
+            'name': self.path
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        args = {
+            'class': 'interface',
+            'interface': 'element',
+            'name': self.interface
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        args = {
+            'class': 'propertyname',
+            'propertyname': 'element',
+            'name': self.method
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        super(Method, self).factory(objs)
+
+    def setup(self, objs):
+        '''Resolve elements.'''
+
+        self.service = get_index(
+            objs,
+            'interface',
+            self.service)
+
+        self.path = get_index(
+            objs,
+            'pathname',
+            self.path)
+
+        self.interface = get_index(
+            objs,
+            'interface',
+            self.interface)
+
+        self.method = get_index(
+            objs,
+            'propertyname',
+            self.method)
+
+        super(Method, self).setup(objs)
+
+    def construct(self, loader, indent):
+        return self.render(
+            loader,
+            'method.mako.cpp',
+            c=self,
+            indent=indent)
+
+
 class CallbackGraphEntry(Group):
     '''An entry in a traversal list for groups of callbacks.'''
 
@@ -724,6 +803,7 @@
             'callback': {
                 'journal': Journal,
                 'group': GroupOfCallbacks,
+                'method': Method,
             },
             'condition': {
                 'count': CountCondition,
diff --git a/src/sdbusplus.hpp b/src/sdbusplus.hpp
index 7dbf44d..8afb8b5 100644
--- a/src/sdbusplus.hpp
+++ b/src/sdbusplus.hpp
@@ -30,6 +30,28 @@
         }
 
     public:
+        /** @brief Invoke a method; ignore reply. */
+        template <typename ...Args>
+        static void callMethodNoReply(
+            const std::string& busName,
+            const std::string& path,
+            const std::string& interface,
+            const std::string& method,
+            Args&& ... args)
+        {
+            auto reqMsg = getBus().new_method_call(
+                              busName.c_str(),
+                              path.c_str(),
+                              interface.c_str(),
+                              method.c_str());
+            reqMsg.append(std::forward<Args>(args)...);
+            getBus().call_noreply(reqMsg);
+
+            // TODO: openbmc/openbmc#1719
+            // invoke these methods async, with a callback
+            // handler that checks for errors and logs.
+        }
+
         /** @brief Invoke a method. */
         template <typename ...Args>
         static auto callMethod(
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 15835bf..9e89983 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -7,6 +7,7 @@
 #include "count.hpp"
 #include "data_types.hpp"
 #include "journal.hpp"
+#include "method.hpp"
 #include "propertywatchimpl.hpp"
 #include "sdbusplus.hpp"
 
diff --git a/src/templates/method.mako.cpp b/src/templates/method.mako.cpp
new file mode 100644
index 0000000..31cdf65
--- /dev/null
+++ b/src/templates/method.mako.cpp
@@ -0,0 +1,6 @@
+makeMethod<SDBusPlus>(
+${indent(1)}ConfigInterfaces::get()[${c.service}],
+${indent(1)}ConfigPaths::get()[${c.path}],
+${indent(1)}ConfigInterfaces::get()[${c.interface}],
+${indent(1)}ConfigProperties::get()[${c.method}],
+${indent(1)}${(',\n' + indent(1)).join([val.argument(loader, indent=indent +1) for val in c.args])})\