|  | /** | 
|  | * Copyright © 2016 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 "manager.hpp" | 
|  | #include "config.h" | 
|  | #include <cassert> | 
|  | #include <iostream> | 
|  | #include <algorithm> | 
|  | #include <thread> | 
|  | #include <chrono> | 
|  | #include "xyz/openbmc_project/Example/Iface1/server.hpp" | 
|  | #include "xyz/openbmc_project/Example/Iface2/server.hpp" | 
|  |  | 
|  | using namespace std::literals::chrono_literals; | 
|  | using namespace std::literals::string_literals; | 
|  |  | 
|  | using Object = phosphor::inventory::manager::Object; | 
|  | using ObjectMap = std::map<sdbusplus::message::object_path, Object>; | 
|  |  | 
|  | constexpr auto MGR_SERVICE = "phosphor.inventory.test.mgr"; | 
|  | constexpr auto MGR_INTERFACE = IFACE; | 
|  | constexpr auto MGR_ROOT = "/testing/inventory"; | 
|  | constexpr auto EXAMPLE_SERVICE = "phosphor.inventory.test.example"; | 
|  | constexpr auto EXAMPLE_ROOT = "/testing"; | 
|  |  | 
|  | const auto trigger1 = sdbusplus::message::object_path(EXAMPLE_ROOT + | 
|  | "/trigger1"s); | 
|  | const auto trigger2 = sdbusplus::message::object_path(EXAMPLE_ROOT + | 
|  | "/trigger2"s); | 
|  | const auto trigger3 = sdbusplus::message::object_path(EXAMPLE_ROOT + | 
|  | "/trigger3"s); | 
|  | const auto trigger4 = sdbusplus::message::object_path(EXAMPLE_ROOT + | 
|  | "/trigger4"s); | 
|  | const auto trigger5 = sdbusplus::message::object_path(EXAMPLE_ROOT + | 
|  | "/trigger5"s); | 
|  |  | 
|  | const sdbusplus::message::object_path relDeleteMeOne{"/deleteme1"}; | 
|  | const sdbusplus::message::object_path relDeleteMeTwo{"/deleteme2"}; | 
|  | const sdbusplus::message::object_path relDeleteMeThree{"/deleteme3"}; | 
|  |  | 
|  | const std::string root{MGR_ROOT}; | 
|  | const std::string deleteMeOne{root + relDeleteMeOne.str}; | 
|  | const std::string deleteMeTwo{root + relDeleteMeTwo.str}; | 
|  | const std::string deleteMeThree{root + relDeleteMeThree.str}; | 
|  |  | 
|  | using ExampleIface1 = sdbusplus::xyz::openbmc_project::Example::server::Iface1; | 
|  | using ExampleIface2 = sdbusplus::xyz::openbmc_project::Example::server::Iface2; | 
|  |  | 
|  | /** @class ExampleService | 
|  | *  @brief Host an object for triggering events. | 
|  | */ | 
|  | struct ExampleService | 
|  | { | 
|  | ~ExampleService() = default; | 
|  | ExampleService() : | 
|  | shutdown(false), | 
|  | bus(sdbusplus::bus::new_default()), | 
|  | objmgr(sdbusplus::server::manager::manager(bus, MGR_ROOT)) | 
|  | { | 
|  | bus.request_name(EXAMPLE_SERVICE); | 
|  | } | 
|  |  | 
|  | void run() | 
|  | { | 
|  | sdbusplus::server::object::object < | 
|  | ExampleIface1, ExampleIface2 > t1(bus, trigger1.str.c_str()); | 
|  | sdbusplus::server::object::object < | 
|  | ExampleIface1, ExampleIface2 > t2(bus, trigger2.str.c_str()); | 
|  | sdbusplus::server::object::object < | 
|  | ExampleIface1, ExampleIface2 > t3(bus, trigger3.str.c_str()); | 
|  | sdbusplus::server::object::object < | 
|  | ExampleIface1, ExampleIface2 > t4(bus, trigger4.str.c_str()); | 
|  | sdbusplus::server::object::object < | 
|  | ExampleIface1, ExampleIface2 > t5(bus, trigger5.str.c_str()); | 
|  |  | 
|  | while (!shutdown) | 
|  | { | 
|  | bus.process_discard(); | 
|  | bus.wait((5000000us).count()); | 
|  | } | 
|  | } | 
|  |  | 
|  | volatile bool shutdown; | 
|  | sdbusplus::bus::bus bus; | 
|  | sdbusplus::server::manager::manager objmgr; | 
|  | }; | 
|  |  | 
|  |  | 
|  | /** @class SignalQueue | 
|  | *  @brief Store DBus signals in a queue. | 
|  | */ | 
|  | class SignalQueue | 
|  | { | 
|  | public: | 
|  | ~SignalQueue() = default; | 
|  | SignalQueue() = delete; | 
|  | SignalQueue(const SignalQueue&) = delete; | 
|  | SignalQueue(SignalQueue&&) = default; | 
|  | SignalQueue& operator=(const SignalQueue&) = delete; | 
|  | SignalQueue& operator=(SignalQueue&&) = default; | 
|  | explicit SignalQueue(const std::string& match) : | 
|  | _bus(sdbusplus::bus::new_default()), | 
|  | _match(_bus, match.c_str(), &callback, this), | 
|  | _next(nullptr) | 
|  | { | 
|  | } | 
|  |  | 
|  | auto&& pop(unsigned timeout = 1000000) | 
|  | { | 
|  | while (timeout > 0 && !_next) | 
|  | { | 
|  | _bus.process_discard(); | 
|  | _bus.wait(50000); | 
|  | timeout -= 50000; | 
|  | } | 
|  | return std::move(_next); | 
|  | } | 
|  |  | 
|  | private: | 
|  | static int callback(sd_bus_message* m, void* context, sd_bus_error*) | 
|  | { | 
|  | auto* me = static_cast<SignalQueue*>(context); | 
|  | sd_bus_message_ref(m); | 
|  | sdbusplus::message::message msg{m}; | 
|  | me->_next = std::move(msg); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | sdbusplus::bus::bus _bus; | 
|  | sdbusplus::bus::match_t _match; | 
|  | sdbusplus::message::message _next; | 
|  | }; | 
|  |  | 
|  | /**@brief Find a subset of interfaces and properties in an object. */ | 
|  | auto hasProperties(const Object& l, const Object& r) | 
|  | { | 
|  | Object result; | 
|  | std::set_difference( | 
|  | r.cbegin(), | 
|  | r.cend(), | 
|  | l.cbegin(), | 
|  | l.cend(), | 
|  | std::inserter(result, result.end())); | 
|  | return result.empty(); | 
|  | } | 
|  |  | 
|  | /**@brief Check an object for one or more interfaces. */ | 
|  | auto hasInterfaces(const std::vector<std::string>& l, const Object& r) | 
|  | { | 
|  | std::vector<std::string> stripped, interfaces; | 
|  | std::transform( | 
|  | r.cbegin(), | 
|  | r.cend(), | 
|  | std::back_inserter(stripped), | 
|  | [](auto & p) | 
|  | { | 
|  | return p.first; | 
|  | }); | 
|  | std::set_difference( | 
|  | stripped.cbegin(), | 
|  | stripped.cend(), | 
|  | l.cbegin(), | 
|  | l.cend(), | 
|  | std::back_inserter(interfaces)); | 
|  | return interfaces.empty(); | 
|  | } | 
|  |  | 
|  | void runTests() | 
|  | { | 
|  | const std::string exampleRoot{EXAMPLE_ROOT}; | 
|  | auto b = sdbusplus::bus::new_default(); | 
|  |  | 
|  | auto notify = [&]() | 
|  | { | 
|  | return b.new_method_call( | 
|  | MGR_SERVICE, | 
|  | MGR_ROOT, | 
|  | MGR_INTERFACE, | 
|  | "Notify"); | 
|  | }; | 
|  | auto set = [&](const std::string & path) | 
|  | { | 
|  | return b.new_method_call( | 
|  | EXAMPLE_SERVICE, | 
|  | path.c_str(), | 
|  | "org.freedesktop.DBus.Properties", | 
|  | "Set"); | 
|  | }; | 
|  |  | 
|  | Object obj | 
|  | { | 
|  | { | 
|  | "xyz.openbmc_project.Example.Iface1", | 
|  | {{"ExampleProperty1", "test1"s}} | 
|  | }, | 
|  | { | 
|  | "xyz.openbmc_project.Example.Iface2", | 
|  | {   {"ExampleProperty2", "test2"s}, | 
|  | {"ExampleProperty3", static_cast<int64_t>(0ll)} | 
|  | } | 
|  | }, | 
|  | }; | 
|  |  | 
|  | // Validate startup events occurred. | 
|  | { | 
|  | sdbusplus::message::object_path relCreateMe3{"/createme3"}; | 
|  | std::string createMe3{root + relCreateMe3.str}; | 
|  |  | 
|  | auto get = b.new_method_call( | 
|  | MGR_SERVICE, | 
|  | createMe3.c_str(), | 
|  | "org.freedesktop.DBus.Properties", | 
|  | "GetAll"); | 
|  | get.append("xyz.openbmc_project.Example.Iface1"); | 
|  | auto resp = b.call(get); | 
|  |  | 
|  | Object::mapped_type properties; | 
|  | assert(!resp.is_method_error()); | 
|  | resp.read(properties); | 
|  | } | 
|  |  | 
|  | // Make sure the notify method works. | 
|  | { | 
|  | sdbusplus::message::object_path relPath{"/foo"}; | 
|  | std::string path(root + relPath.str); | 
|  |  | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesAdded'"); | 
|  |  | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relPath, obj}})); | 
|  | b.call(m); | 
|  |  | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sdbusplus::message::object_path signalPath; | 
|  | Object signalObjectType; | 
|  | sig.read(signalPath); | 
|  | assert(path == signalPath.str); | 
|  | sig.read(signalObjectType); | 
|  | assert(hasProperties(signalObjectType, obj)); | 
|  | auto moreSignals{queue.pop()}; | 
|  | assert(!moreSignals); | 
|  | } | 
|  |  | 
|  | // Validate the propertyIs filter. | 
|  | { | 
|  | // Create an object to be deleted. | 
|  | { | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relDeleteMeThree, obj}})); | 
|  | b.call(m); | 
|  | } | 
|  |  | 
|  | // Validate that the action does not run if the property doesn't match. | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger4.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("123")); | 
|  | b.call(m); | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  |  | 
|  | // Validate that the action does run if the property matches. | 
|  | { | 
|  | // Set ExampleProperty2 to something else to the 123 filter | 
|  | // matches. | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger4.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("xyz")); | 
|  | b.call(m); | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  | { | 
|  | // Set ExampleProperty3 to 99. | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger4.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty3"); | 
|  | m.append(sdbusplus::message::variant<int64_t>(99)); | 
|  | b.call(m); | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger4.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("123")); | 
|  | b.call(m); | 
|  |  | 
|  | sdbusplus::message::object_path sigpath; | 
|  | std::vector<std::string> interfaces; | 
|  | { | 
|  | std::vector<std::string> interfaces; | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(sigpath); | 
|  | assert(sigpath == deleteMeThree); | 
|  | sig.read(interfaces); | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | assert(hasInterfaces(interfaces, obj)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Make sure DBus signals are handled. | 
|  | { | 
|  | // Create some objects to be deleted by an action. | 
|  | { | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relDeleteMeOne, obj}})); | 
|  | b.call(m); | 
|  | } | 
|  | { | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relDeleteMeTwo, obj}})); | 
|  | b.call(m); | 
|  | } | 
|  | { | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relDeleteMeThree, obj}})); | 
|  | b.call(m); | 
|  | } | 
|  |  | 
|  | // Set some properties that should not trigger due to a filter. | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger1.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("abc123")); | 
|  | b.call(m); | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  | auto m = set(trigger3.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty3"); | 
|  | m.append(sdbusplus::message::variant<int64_t>(11)); | 
|  | b.call(m); | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  |  | 
|  | // Set some properties that should trigger. | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  |  | 
|  | auto m = set(trigger1.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("xxxyyy")); | 
|  | b.call(m); | 
|  |  | 
|  | sdbusplus::message::object_path sigpath; | 
|  | std::vector<std::string> interfaces; | 
|  | { | 
|  | std::vector<std::string> interfaces; | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(sigpath); | 
|  | assert(sigpath == deleteMeOne); | 
|  | sig.read(interfaces); | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | assert(hasInterfaces(interfaces, obj)); | 
|  | } | 
|  | { | 
|  | std::vector<std::string> interfaces; | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(sigpath); | 
|  | assert(sigpath == deleteMeTwo); | 
|  | sig.read(interfaces); | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | assert(hasInterfaces(interfaces, obj)); | 
|  | } | 
|  | { | 
|  | // Make sure there were only two signals. | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  | } | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesRemoved'"); | 
|  |  | 
|  | auto m = set(trigger3.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty3"); | 
|  | m.append(sdbusplus::message::variant<int64_t>(10)); | 
|  | b.call(m); | 
|  |  | 
|  | sdbusplus::message::object_path sigpath; | 
|  | std::vector<std::string> interfaces; | 
|  | { | 
|  | std::vector<std::string> interfaces; | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(sigpath); | 
|  | assert(sigpath == deleteMeThree); | 
|  | sig.read(interfaces); | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | assert(hasInterfaces(interfaces, obj)); | 
|  | } | 
|  | { | 
|  | // Make sure there was only one signal. | 
|  | auto sig{queue.pop()}; | 
|  | assert(!sig); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Validate the set property action. | 
|  | { | 
|  | sdbusplus::message::object_path relChangeMe{"/changeme"}; | 
|  | std::string changeMe{root + relChangeMe.str}; | 
|  |  | 
|  | // Create an object to be updated by the set property action. | 
|  | { | 
|  | auto m = notify(); | 
|  | m.append(ObjectMap({{relChangeMe, obj}})); | 
|  | b.call(m); | 
|  | } | 
|  |  | 
|  | // Trigger and validate the change. | 
|  | { | 
|  | SignalQueue queue( | 
|  | "path='" + changeMe + "',member='PropertiesChanged'"); | 
|  | auto m = set(trigger2.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("yyyxxx")); | 
|  | b.call(m); | 
|  |  | 
|  | std::string sigInterface; | 
|  | std::map < | 
|  | std::string, | 
|  | sdbusplus::message::variant<std::string >> sigProperties; | 
|  | { | 
|  | std::vector<std::string> interfaces; | 
|  | auto sig{queue.pop()}; | 
|  | sig.read(sigInterface); | 
|  | assert(sigInterface == "xyz.openbmc_project.Example.Iface1"); | 
|  | sig.read(sigProperties); | 
|  | assert(sigProperties["ExampleProperty1"] == "changed"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Validate the create object action. | 
|  | { | 
|  | sdbusplus::message::object_path relCreateMe1{"/createme1"}; | 
|  | sdbusplus::message::object_path relCreateMe2{"/createme2"}; | 
|  | std::string createMe1{root + relCreateMe1.str}; | 
|  | std::string createMe2{root + relCreateMe2.str}; | 
|  |  | 
|  | // Trigger the action. | 
|  | { | 
|  | sdbusplus::message::object_path signalPath; | 
|  | Object signalObject; | 
|  |  | 
|  | SignalQueue queue( | 
|  | "path='" + root + "',member='InterfacesAdded'"); | 
|  |  | 
|  | auto m = set(trigger5.str); | 
|  | m.append("xyz.openbmc_project.Example.Iface2"); | 
|  | m.append("ExampleProperty2"); | 
|  | m.append(sdbusplus::message::variant<std::string>("abc123")); | 
|  | b.call(m); | 
|  | { | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(signalPath); | 
|  | assert(createMe1 == signalPath.str); | 
|  | sig.read(signalObject); | 
|  | } | 
|  | { | 
|  | auto sig{queue.pop()}; | 
|  | assert(static_cast<bool>(sig)); | 
|  | sig.read(signalPath); | 
|  | assert(createMe2 == signalPath.str); | 
|  | sig.read(signalObject); | 
|  | } | 
|  |  | 
|  | auto moreSignals{queue.pop()}; | 
|  | assert(!moreSignals); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int main() | 
|  | { | 
|  | phosphor::inventory::manager::Manager mgr( | 
|  | sdbusplus::bus::new_default(), | 
|  | MGR_SERVICE, MGR_ROOT, MGR_INTERFACE); | 
|  | ExampleService d; | 
|  |  | 
|  | auto f1 = [](auto mgr) | 
|  | { | 
|  | mgr->run(); | 
|  | }; | 
|  | auto f2 = [](auto d) | 
|  | { | 
|  | d->run(); | 
|  | }; | 
|  |  | 
|  | auto t1 = std::thread(f1, &mgr); | 
|  | auto t2 = std::thread(f2, &d); | 
|  |  | 
|  | runTests(); | 
|  |  | 
|  | mgr.shutdown(); | 
|  | d.shutdown = true; | 
|  |  | 
|  | // Wait for server threads to exit. | 
|  | t1.join(); | 
|  | t2.join(); | 
|  | std::cout << "Success!  Waiting for threads to exit..." << std::endl; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 |