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_));