buffet: Add DBusCommandDispacher and hook it up to CommandQueue Added DBusCommandDispacher class that maintains DBusCommandProxy object for each instance of CommandInstance class added to the CommandQueue. As soon as a command instance is removed from the queue, D-Bus command dispatcher removes the corresponding command proxy object from D-Bus. BUG=chromium:374864 TEST=FEATURES=test emerge-link buffet Change-Id: Ib7ce7370bd3ee471e22f02b8546675021ff063d7 Reviewed-on: https://chromium-review.googlesource.com/211642 Tested-by: Alex Vakulenko <avakulenko@chromium.org> Reviewed-by: Christopher Wiley <wiley@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index f245e5e..0a10afe 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -18,6 +18,7 @@ 'commands/command_instance.cc', 'commands/command_manager.cc', 'commands/command_queue.cc', + 'commands/dbus_command_dispatcher.cc', 'commands/dbus_command_proxy.cc', 'commands/object_schema.cc', 'commands/prop_constraints.cc', @@ -76,6 +77,7 @@ 'commands/command_instance_unittest.cc', 'commands/command_manager_unittest.cc', 'commands/command_queue_unittest.cc', + 'commands/dbus_command_dispatcher_unittest.cc', 'commands/dbus_command_proxy_unittest.cc', 'commands/object_schema_unittest.cc', 'commands/schema_utils_unittest.cc',
diff --git a/buffet/commands/command_manager.cc b/buffet/commands/command_manager.cc index 3d55906..ae105b8 100644 --- a/buffet/commands/command_manager.cc +++ b/buffet/commands/command_manager.cc
@@ -8,13 +8,26 @@ #include <base/files/file_enumerator.h> #include <base/json/json_reader.h> #include <base/values.h> +#include <chromeos/dbus/exported_object_manager.h> #include <chromeos/errors/error.h> #include <chromeos/errors/error_codes.h> #include "buffet/commands/schema_constants.h" +using chromeos::dbus_utils::ExportedObjectManager; + namespace buffet { +CommandManager::CommandManager() { + command_queue_.SetCommandDispachInterface(&command_dispatcher_); +} + +CommandManager::CommandManager( + const base::WeakPtr<ExportedObjectManager>& object_manager) + : command_dispatcher_(object_manager->GetBus(), object_manager.get()) { + command_queue_.SetCommandDispachInterface(&command_dispatcher_); +} + const CommandDictionary& CommandManager::GetCommandDictionary() const { return dictionary_; } @@ -104,4 +117,9 @@ return std::unique_ptr<const base::DictionaryValue>(dict_value); } +std::string CommandManager::AddCommand( + std::unique_ptr<CommandInstance> command_instance) { + return command_queue_.Add(std::move(command_instance)); +} + } // namespace buffet
diff --git a/buffet/commands/command_manager.h b/buffet/commands/command_manager.h index d3f9a34..601ae9a 100644 --- a/buffet/commands/command_manager.h +++ b/buffet/commands/command_manager.h
@@ -5,21 +5,37 @@ #ifndef BUFFET_COMMANDS_COMMAND_MANAGER_H_ #define BUFFET_COMMANDS_COMMAND_MANAGER_H_ +#include <memory> #include <string> -#include <base/basictypes.h> #include <base/files/file_path.h> +#include <base/macros.h> +#include <base/memory/weak_ptr.h> #include "buffet/commands/command_dictionary.h" +#include "buffet/commands/command_queue.h" +#include "buffet/commands/dbus_command_dispatcher.h" + +namespace chromeos { +namespace dbus_utils { +class ExportedObjectManager; +} // namespace dbus_utils +} // namespace chromeos + namespace buffet { +class CommandInstance; + // CommandManager class that will have a list of all the device command // schemas as well as the live command queue of pending command instances // dispatched to the device. -class CommandManager { +class CommandManager final { public: - CommandManager() = default; + CommandManager(); + explicit CommandManager( + const base::WeakPtr<chromeos::dbus_utils::ExportedObjectManager>& + object_manager); // Get the command definitions for the device. const CommandDictionary& GetCommandDictionary() const; @@ -59,6 +75,9 @@ // the current device. void Startup(); + // Adds a new command to the command queue. Returns command ID. + std::string AddCommand(std::unique_ptr<CommandInstance> command_instance); + private: // Helper function to load a JSON file that is expected to be // an object/dictionary. In case of error, returns empty unique ptr and fills @@ -68,6 +87,8 @@ CommandDictionary base_dictionary_; // Base/std command definitions/schemas. CommandDictionary dictionary_; // Command definitions/schemas. + CommandQueue command_queue_; + DBusCommandDispacher command_dispatcher_; DISALLOW_COPY_AND_ASSIGN(CommandManager); };
diff --git a/buffet/commands/dbus_command_dispatcher.cc b/buffet/commands/dbus_command_dispatcher.cc new file mode 100644 index 0000000..e6041d2 --- /dev/null +++ b/buffet/commands/dbus_command_dispatcher.cc
@@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "buffet/commands/dbus_command_dispatcher.h" + +#include <chromeos/dbus/exported_object_manager.h> + +#include "buffet/commands/command_instance.h" + +using chromeos::dbus_utils::AsyncEventSequencer; +using chromeos::dbus_utils::ExportedObjectManager; + +namespace buffet { + +DBusCommandDispacher::DBusCommandDispacher( + const scoped_refptr<dbus::Bus>& bus, + ExportedObjectManager* object_manager) + : bus_(bus) { + if (object_manager) + object_manager_ = object_manager->AsWeakPtr(); +} + +void DBusCommandDispacher::OnCommandAdded(CommandInstance* command_instance) { + auto proxy = CreateDBusCommandProxy(command_instance); + proxy->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction()); + command_instance->SetProxy(proxy.get()); + + auto pair = std::make_pair(command_instance, std::move(proxy)); + CHECK(command_map_.insert(std::move(pair)).second) + << "The command instance is already in the dispatcher command map"; +} + +void DBusCommandDispacher::OnCommandRemoved(CommandInstance* command_instance) { + command_instance->SetProxy(nullptr); + CHECK_GT(command_map_.erase(command_instance), 0u) + << "The command instance is not in the dispatcher command map"; +} + +DBusCommandProxy* DBusCommandDispacher::FindProxy( + CommandInstance* command_instance) const { + auto p = command_map_.find(command_instance); + return p != command_map_.end() ? p->second.get() : nullptr; +} + +std::unique_ptr<DBusCommandProxy> DBusCommandDispacher::CreateDBusCommandProxy( + CommandInstance* command_instance) const { + return std::unique_ptr<DBusCommandProxy>( + new DBusCommandProxy(object_manager_.get(), bus_, command_instance)); +} + +} // namespace buffet
diff --git a/buffet/commands/dbus_command_dispatcher.h b/buffet/commands/dbus_command_dispatcher.h new file mode 100644 index 0000000..94f938b --- /dev/null +++ b/buffet/commands/dbus_command_dispatcher.h
@@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUFFET_COMMANDS_DBUS_COMMAND_DISPATCHER_H_ +#define BUFFET_COMMANDS_DBUS_COMMAND_DISPATCHER_H_ + +#include <map> +#include <string> + +#include <base/macros.h> +#include <base/memory/weak_ptr.h> +#include <dbus/bus.h> + +#include "buffet/commands/command_dispatch_interface.h" +#include "buffet/commands/dbus_command_proxy.h" + +namespace chromeos { +namespace dbus_utils { +class ExportedObjectManager; +} // namespace dbus_utils +} // namespace chromeos + +namespace buffet { + +// Implements D-Bus dispatch of commands. When OnCommandAdded is called over +// CommandDispachInterface, DBusCommandDispacher creates an instance of +// DBusCommandProxy object and advertises it through ExportedObjectManager on +// D-Bus. Command handling processes can watch the new D-Bus object appear +// and communicate with it to update the command handling progress. +// Once command is handled, DBusCommandProxy::Done() is called and the command +// is removed from the command queue and D-Bus ExportedObjectManager. +class DBusCommandDispacher : public CommandDispachInterface { + public: + DBusCommandDispacher( + const scoped_refptr<dbus::Bus>& bus, + chromeos::dbus_utils::ExportedObjectManager* object_manager = nullptr); + virtual ~DBusCommandDispacher() = default; + + // CommandDispachInterface overrides. Called by CommandQueue. + void OnCommandAdded(CommandInstance* command_instance) override; + void OnCommandRemoved(CommandInstance* command_instance) override; + + // Finds a D-Bus command proxy for the given command instance. + // Returns nullptr if the proxy does not exist. + DBusCommandProxy* FindProxy(CommandInstance* command_instance) const; + + protected: + virtual std::unique_ptr<DBusCommandProxy> CreateDBusCommandProxy( + CommandInstance* command_instance) const; + + private: + scoped_refptr<dbus::Bus> bus_; + base::WeakPtr<chromeos::dbus_utils::ExportedObjectManager> object_manager_; + // This is the map that tracks relationship between CommandInstance and + // corresponding DBusCommandProxy objects. + std::map<CommandInstance*, std::unique_ptr<DBusCommandProxy>> command_map_; + + // Default constructor is used in special circumstances such as for testing. + DBusCommandDispacher() = default; + + friend class DBusCommandDispacherTest; + friend class CommandManager; + DISALLOW_COPY_AND_ASSIGN(DBusCommandDispacher); +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_DBUS_COMMAND_DISPATCHER_H_
diff --git a/buffet/commands/dbus_command_dispatcher_unittest.cc b/buffet/commands/dbus_command_dispatcher_unittest.cc new file mode 100644 index 0000000..dd52871 --- /dev/null +++ b/buffet/commands/dbus_command_dispatcher_unittest.cc
@@ -0,0 +1,196 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> +#include <string> + +#include <chromeos/dbus/exported_object_manager.h> +#include <dbus/mock_bus.h> +#include <dbus/mock_exported_object.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <gtest/gtest.h> + +#include "buffet/commands/command_dictionary.h" +#include "buffet/commands/command_queue.h" +#include "buffet/commands/dbus_command_dispatcher.h" +#include "buffet/commands/unittest_utils.h" +#include "buffet/dbus_constants.h" + +using buffet::unittests::CreateDictionaryValue; +using chromeos::dbus_utils::AsyncEventSequencer; +using testing::AnyNumber; +using testing::InSequence; +using testing::Invoke; +using testing::Return; +using testing::_; + +namespace buffet { + +namespace { + +const char kCommandCategory[] = "test_category"; + +} // anonymous namespace + +class DBusCommandDispacherTest : public testing::Test { + public: + void SetUp() override { + const dbus::ObjectPath kExportedObjectManagerPath("/test/om_path"); + std::string cmd_path = dbus_constants::kCommandServicePathPrefix; + cmd_path += "1"; + const dbus::ObjectPath kCmdObjPath(cmd_path); + + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object manager. + mock_exported_object_manager_ = new dbus::MockExportedObject( + bus_.get(), kExportedObjectManagerPath); + EXPECT_CALL(*bus_, GetExportedObject(kExportedObjectManagerPath)) + .WillRepeatedly(Return(mock_exported_object_manager_.get())); + EXPECT_CALL(*mock_exported_object_manager_, + ExportMethod(_, _, _, _)).Times(AnyNumber()); + om_.reset(new chromeos::dbus_utils::ExportedObjectManager( + bus_.get(), kExportedObjectManagerPath)); + om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction()); + command_dispatcher_.reset( + new DBusCommandDispacher(om_->GetBus(), om_.get())); + command_queue_.SetCommandDispachInterface(command_dispatcher_.get()); + // Use a mock exported object for command proxy. + mock_exported_command_proxy_ = new dbus::MockExportedObject( + bus_.get(), kCmdObjPath); + EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath)) + .WillRepeatedly(Return(mock_exported_command_proxy_.get())); + EXPECT_CALL(*mock_exported_command_proxy_, ExportMethod(_, _, _, _)) + .WillRepeatedly(Invoke(MockExportMethod)); + + auto json = CreateDictionaryValue(R"({ + 'base': { + 'reboot': { + 'parameters': {'delay': 'integer'} + }, + 'shutdown': { + 'parameters': {} + } + } + })"); + CHECK(dictionary_.LoadCommands(*json, kCommandCategory, nullptr, nullptr)) + << "Failed to load command dictionary"; + } + + void TearDown() override { + EXPECT_CALL(*mock_exported_object_manager_, Unregister()).Times(1); + om_.reset(); + bus_ = nullptr; + } + + static void MockExportMethod( + const std::string& interface_name, + const std::string& method_name, + dbus::ExportedObject::MethodCallCallback method_call_callback, + dbus::ExportedObject::OnExportedCallback on_exported_callback) { + on_exported_callback.Run(interface_name, method_name, true); + } + + + std::string AddNewCommand(const std::string& json) { + auto command_instance = CommandInstance::FromJson( + CreateDictionaryValue(json.c_str()).get(), dictionary_, nullptr); + // Two interfaces are added - Command and Properties. + EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2); + return command_instance ? + command_queue_.Add(std::move(command_instance)) : std::string(); + } + + void FinishCommand(DBusCommandProxy* proxy) { + proxy->HandleDone(nullptr); + } + + void SetProgress(DBusCommandProxy* proxy, int progress) { + proxy->HandleSetProgress(nullptr, progress); + } + + + scoped_refptr<dbus::MockBus> bus_; + scoped_refptr<dbus::MockExportedObject> mock_exported_object_manager_; + scoped_refptr<dbus::MockExportedObject> mock_exported_command_proxy_; + std::unique_ptr<chromeos::dbus_utils::ExportedObjectManager> om_; + CommandDictionary dictionary_; + CommandQueue command_queue_; + std::unique_ptr<DBusCommandDispacher> command_dispatcher_; +}; + +TEST_F(DBusCommandDispacherTest, Test_Command_Base_Shutdown) { + std::string id = AddNewCommand("{'name':'base.shutdown'}"); + EXPECT_EQ("1", id); + CommandInstance* command_instance = command_queue_.Find(id); + ASSERT_NE(nullptr, command_instance); + DBusCommandProxy* command_proxy = + command_dispatcher_->FindProxy(command_instance); + ASSERT_NE(nullptr, command_proxy); + EXPECT_EQ(CommandInstance::kStatusQueued, command_instance->GetStatus()); + + // Two properties are set, Progress = 50%, Status = "inProgress" + EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2); + SetProgress(command_proxy, 50); + EXPECT_EQ(CommandInstance::kStatusInProgress, command_instance->GetStatus()); + EXPECT_EQ(50, command_instance->GetProgress()); + + // Command must be removed from the queue and proxy destroyed after calling + // FinishCommand(). + // Two properties are set, Progress = 100%, Status = "done" + EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2); + // D-Bus command proxy is going away. + EXPECT_CALL(*mock_exported_command_proxy_, Unregister()).Times(1); + // Two interfaces are being removed on the D-Bus command object. + EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2); + FinishCommand(command_proxy); + + EXPECT_EQ(nullptr, + command_dispatcher_->FindProxy(command_instance)); + EXPECT_EQ(nullptr, command_queue_.Find(id)); +} + +TEST_F(DBusCommandDispacherTest, Test_Command_Base_Reboot) { + std::string id = AddNewCommand(R"({ + 'name': 'base.reboot', + 'parameters': { + 'delay': 20 + } + })"); + EXPECT_EQ("1", id); + CommandInstance* command_instance = command_queue_.Find(id); + ASSERT_NE(nullptr, command_instance); + DBusCommandProxy* command_proxy = + command_dispatcher_->FindProxy(command_instance); + ASSERT_NE(nullptr, command_proxy); + EXPECT_EQ(CommandInstance::kStatusQueued, command_instance->GetStatus()); + + // Two properties are set, Progress = 50%, Status = "inProgress" + EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2); + SetProgress(command_proxy, 50); + EXPECT_EQ(CommandInstance::kStatusInProgress, command_instance->GetStatus()); + EXPECT_EQ(50, command_instance->GetProgress()); + + // Command must be removed from the queue and proxy destroyed after calling + // FinishCommand(). + // Two properties are set, Progress = 100%, Status = "done" + EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2); + // D-Bus command proxy is going away. + EXPECT_CALL(*mock_exported_command_proxy_, Unregister()).Times(1); + // Two interfaces are being removed on the D-Bus command object. + EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2); + FinishCommand(command_proxy); + + EXPECT_EQ(nullptr, + command_dispatcher_->FindProxy(command_instance)); + EXPECT_EQ(nullptr, command_queue_.Find(id)); +} + + +} // namespace buffet
diff --git a/buffet/manager.cc b/buffet/manager.cc index ac693f1..7c3cbd5 100644 --- a/buffet/manager.cc +++ b/buffet/manager.cc
@@ -56,7 +56,8 @@ // the properties interface. state_.SetValue("{}"); dbus_object_.RegisterAsync(cb); - command_manager_ = std::make_shared<CommandManager>(); + command_manager_ = + std::make_shared<CommandManager>(dbus_object_.GetObjectManager()); command_manager_->Startup(); device_info_ = std::unique_ptr<DeviceRegistrationInfo>( new DeviceRegistrationInfo(command_manager_));