buffet: Add DBus proxy class for command instance object Added DBusCommandProxy class that implements org.chromium.Buffet.Command DBus interface, including command methods and properties. BUG=chromium:374864 TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2 Change-Id: Iaf17f2b7c276edc1e9f3ca09a759a4a7d4dc3b10 Reviewed-on: https://chromium-review.googlesource.com/213267 Tested-by: Alex Vakulenko <avakulenko@chromium.org> Reviewed-by: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index b95637f..74c1564 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -21,6 +21,7 @@ 'commands/command_instance.cc', 'commands/command_manager.cc', 'commands/command_queue.cc', + 'commands/dbus_command_proxy.cc', 'commands/object_schema.cc', 'commands/prop_constraints.cc', 'commands/prop_types.cc', @@ -68,6 +69,11 @@ 'dependencies': [ 'buffet_common', ], + 'variables': { + 'deps': [ + 'libchrome-test-<(libbase_ver)', + ], + }, 'includes': ['../common-mk/common_test.gypi'], 'sources': [ 'any_unittest.cc', @@ -78,6 +84,7 @@ 'commands/command_instance_unittest.cc', 'commands/command_manager_unittest.cc', 'commands/command_queue_unittest.cc', + 'commands/dbus_command_proxy_unittest.cc', 'commands/object_schema_unittest.cc', 'commands/schema_utils_unittest.cc', 'commands/unittest_utils.cc',
diff --git a/buffet/commands/dbus_command_proxy.cc b/buffet/commands/dbus_command_proxy.cc new file mode 100644 index 0000000..9204710 --- /dev/null +++ b/buffet/commands/dbus_command_proxy.cc
@@ -0,0 +1,102 @@ +// 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_proxy.h" + +#include <chromeos/async_event_sequencer.h> +#include <chromeos/dbus_utils.h> +#include <chromeos/exported_object_manager.h> + +#include "buffet/commands/command_instance.h" +#include "buffet/commands/prop_constraints.h" +#include "buffet/commands/prop_types.h" +#include "buffet/dbus_constants.h" + +using chromeos::dbus_utils::AsyncEventSequencer; +using chromeos::dbus_utils::ExportedObjectManager; + +namespace buffet { + +DBusCommandProxy::DBusCommandProxy(ExportedObjectManager* object_manager, + const scoped_refptr<dbus::Bus>& bus, + CommandInstance* command_instance) + : object_path_(dbus_constants::kCommandServicePathPrefix + + command_instance->GetID()), + command_instance_(command_instance), + dbus_object_(object_manager, bus, object_path_) { +} + +void DBusCommandProxy::RegisterAsync( + const AsyncEventSequencer::CompletionAction& completion_callback) { + chromeos::dbus_utils::DBusInterface* itf = + dbus_object_.AddOrGetInterface(dbus_constants::kCommandInterface); + + // DBus methods. + itf->AddMethodHandler(dbus_constants::kCommandSetProgress, + base::Unretained(this), + &DBusCommandProxy::HandleSetProgress); + itf->AddMethodHandler(dbus_constants::kCommandAbort, + base::Unretained(this), + &DBusCommandProxy::HandleAbort); + itf->AddMethodHandler(dbus_constants::kCommandCancel, + base::Unretained(this), + &DBusCommandProxy::HandleCancel); + itf->AddMethodHandler(dbus_constants::kCommandDone, + base::Unretained(this), + &DBusCommandProxy::HandleDone); + + // DBus properties. + itf->AddProperty(dbus_constants::kCommandName, &name_); + itf->AddProperty(dbus_constants::kCommandCategory, &category_); + itf->AddProperty(dbus_constants::kCommandId, &id_); + itf->AddProperty(dbus_constants::kCommandStatus, &status_); + itf->AddProperty(dbus_constants::kCommandProgress, &progress_); + + // Set the initial property values before registering the DBus object. + name_.SetValue(command_instance_->GetName()); + category_.SetValue(command_instance_->GetCategory()); + id_.SetValue(command_instance_->GetID()); + status_.SetValue(dbus_constants::kCommandStatusQueued); + progress_.SetValue(0); + + // Register the command DBus object and expose its methods and properties. + dbus_object_.RegisterAsync(completion_callback); +} + +void DBusCommandProxy::HandleSetProgress(chromeos::ErrorPtr* error, + int32_t progress) { + LOG(INFO) << "Received call to Command<" + << command_instance_->GetName() << ">::SetProgress(" + << progress << ")"; + + // Validate |progress| parameter. Its value must be between 0 and 100. + IntPropType progress_type; + progress_type.AddMinMaxConstraint(0, 100); + if (progress_type.ValidateValue(progress, error)) { + status_.SetValue(dbus_constants::kCommandStatusInProgress); + progress_.SetValue(progress); + } +} + +void DBusCommandProxy::HandleAbort(chromeos::ErrorPtr* error) { + LOG(INFO) << "Received call to Command<" + << command_instance_->GetName() << ">::Abort()"; + status_.SetValue(dbus_constants::kCommandStatusAborted); +} + +void DBusCommandProxy::HandleCancel(chromeos::ErrorPtr* error) { + LOG(INFO) << "Received call to Command<" + << command_instance_->GetName() << ">::Cancel()"; + status_.SetValue(dbus_constants::kCommandStatusCanceled); +} + +void DBusCommandProxy::HandleDone(chromeos::ErrorPtr* error) { + LOG(INFO) << "Received call to Command<" + << command_instance_->GetName() << ">::Done()"; + status_.SetValue(dbus_constants::kCommandStatusDone); + progress_.SetValue(100); +} + + +} // namespace buffet
diff --git a/buffet/commands/dbus_command_proxy.h b/buffet/commands/dbus_command_proxy.h new file mode 100644 index 0000000..a4b2a53 --- /dev/null +++ b/buffet/commands/dbus_command_proxy.h
@@ -0,0 +1,63 @@ +// 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_PROXY_H_ +#define BUFFET_COMMANDS_DBUS_COMMAND_PROXY_H_ + +#include <memory> +#include <string> + +#include <base/basictypes.h> +#include <chromeos/dbus/dbus_object.h> + +namespace chromeos { +namespace dbus_utils { +class ExportedObjectManager; +} // namespace dbus_utils +} // namespace chromeos + +namespace buffet { + +class CommandInstance; + +class DBusCommandProxy { + public: + DBusCommandProxy(chromeos::dbus_utils::ExportedObjectManager* object_manager, + const scoped_refptr<dbus::Bus>& bus, + CommandInstance* command_instance); + virtual ~DBusCommandProxy() = default; + + void RegisterAsync( + const chromeos::dbus_utils::AsyncEventSequencer::CompletionAction& + completion_callback); + + private: + // DBus properties for org.chromium.Buffet.Command interface. + chromeos::dbus_utils::ExportedProperty<std::string> name_; + chromeos::dbus_utils::ExportedProperty<std::string> category_; + chromeos::dbus_utils::ExportedProperty<std::string> id_; + chromeos::dbus_utils::ExportedProperty<std::string> status_; + chromeos::dbus_utils::ExportedProperty<int32_t> progress_; + + // Handles calls to org.chromium.Buffet.Command.SetProgress(progress). + void HandleSetProgress(chromeos::ErrorPtr* error, int32_t progress); + // Handles calls to org.chromium.Buffet.Command.Abort(). + void HandleAbort(chromeos::ErrorPtr* error); + // Handles calls to org.chromium.Buffet.Command.Cancel(). + void HandleCancel(chromeos::ErrorPtr* error); + // Handles calls to org.chromium.Buffet.Command.Done(). + void HandleDone(chromeos::ErrorPtr* error); + + dbus::ObjectPath object_path_; + CommandInstance* command_instance_; + + chromeos::dbus_utils::DBusObject dbus_object_; + + friend class DBusCommandProxyTest; + DISALLOW_COPY_AND_ASSIGN(DBusCommandProxy); +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_DBUS_COMMAND_PROXY_H_
diff --git a/buffet/commands/dbus_command_proxy_unittest.cc b/buffet/commands/dbus_command_proxy_unittest.cc new file mode 100644 index 0000000..8dd11a6 --- /dev/null +++ b/buffet/commands/dbus_command_proxy_unittest.cc
@@ -0,0 +1,230 @@ +// 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 <functional> +#include <memory> + +#include <dbus/mock_bus.h> +#include <dbus/mock_exported_object.h> +#include <dbus/property.h> +#include <chromeos/dbus/dbus_object.h> +#include <gtest/gtest.h> + +#include "buffet/commands/command_dictionary.h" +#include "buffet/commands/command_instance.h" +#include "buffet/commands/dbus_command_proxy.h" +#include "buffet/commands/unittest_utils.h" +#include "buffet/dbus_constants.h" + +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::_; + +using chromeos::dbus_utils::ExportedObjectManager; +using buffet::unittests::CreateDictionaryValue; + +namespace buffet { + +namespace { + +const char kTestCommandCategoty[] = "test_command_category"; +const char kTestCommandId[] = "cmd_1"; + +void NoAction(bool all_succeeded) {} + +} // namespace + +class DBusCommandProxyTest : public ::testing::Test { + public: + void SetUp() override { + // Set up a mock DBus bus object. + 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()); + + // Command instance. + auto json = CreateDictionaryValue(R"({ + 'robot': { + 'jump': { + 'parameters': { + 'height': { + 'type': 'integer', + 'minimum': 0, + 'maximum': 100 + }, + '_jumpType': { + 'type': 'string', + 'enum': ['_withAirFlip', '_withSpin', '_withKick'] + } + } + } + } + })"); + CHECK(dict_.LoadCommands(*json, kTestCommandCategoty, nullptr, nullptr)) + << "Failed to parse test command dictionary"; + + json = CreateDictionaryValue(R"({ + 'name': 'robot.jump', + 'parameters': { + 'height': 53, + '_jumpType': '_withKick' + } + })"); + command_instance_ = CommandInstance::FromJson(json.get(), dict_, nullptr); + command_instance_->SetID(kTestCommandId); + + // Set up a mock ExportedObject to be used with the DBus command proxy. + std::string cmd_path = dbus_constants::kCommandServicePathPrefix; + cmd_path += kTestCommandId; + const dbus::ObjectPath kCmdObjPath(cmd_path); + // Use a mock exported object for the exported object manager. + mock_exported_object_command_ = + new dbus::MockExportedObject(bus_.get(), kCmdObjPath); + EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath)).Times(AnyNumber()) + .WillRepeatedly(Return(mock_exported_object_command_.get())); + EXPECT_CALL(*mock_exported_object_command_, + ExportMethod(_, _, _, _)).Times(AnyNumber()); + + command_proxy_.reset(new DBusCommandProxy(nullptr, bus_, + command_instance_.get())); + command_proxy_->RegisterAsync(base::Bind(NoAction)); + } + + void TearDown() override { + EXPECT_CALL(*mock_exported_object_command_, Unregister()).Times(1); + command_proxy_.reset(); + command_instance_.reset(); + dict_.Clear(); + bus_ = nullptr; + } + + chromeos::dbus_utils::DBusObject* GetProxyDBusObject() { + return &command_proxy_->dbus_object_; + } + + std::string GetStatus() const { + return command_proxy_->status_.value(); + } + + int32_t GetProgress() const { + return command_proxy_->progress_.value(); + } + + std::unique_ptr<dbus::Response> CallMethod( + const std::string& method_name, + const std::function<void(dbus::MessageWriter*)>& param_callback) { + dbus::MethodCall method_call(dbus_constants::kCommandInterface, + method_name); + method_call.SetSerial(1234); + dbus::MessageWriter writer(&method_call); + if (param_callback) + param_callback(&writer); + return chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(), + &method_call); + } + + static bool IsResponseError(const std::unique_ptr<dbus::Response>& response) { + return (response->GetMessageType() == dbus::Message::MESSAGE_ERROR); + } + + static void VerifyResponse( + const std::unique_ptr<dbus::Response>& response, + const std::function<void(dbus::MessageReader*)>& result_callback) { + EXPECT_FALSE(IsResponseError(response)); + dbus::MessageReader reader(response.get()); + if (result_callback) + result_callback(&reader); + EXPECT_FALSE(reader.HasMoreData()); + } + + template<typename T> + T GetPropertyValue(const std::string& property_name) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + method_call.SetSerial(1234); + dbus::MessageWriter writer(&method_call); + writer.AppendString(dbus_constants::kCommandInterface); + writer.AppendString(property_name); + auto response = chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(), + &method_call); + T value{}; + VerifyResponse(response, [&value](dbus::MessageReader* reader) { + EXPECT_TRUE(chromeos::dbus_utils::PopValueFromReader(reader, &value)); + }); + return value; + } + + std::unique_ptr<DBusCommandProxy> command_proxy_; + std::unique_ptr<CommandInstance> command_instance_; + CommandDictionary dict_; + + scoped_refptr<dbus::MockExportedObject> mock_exported_object_command_; + scoped_refptr<dbus::MockBus> bus_; +}; + +TEST_F(DBusCommandProxyTest, Init) { + EXPECT_EQ(dbus_constants::kCommandStatusQueued, GetStatus()); + EXPECT_EQ(0, GetProgress()); + EXPECT_EQ("robot.jump", + GetPropertyValue<std::string>(dbus_constants::kCommandName)); + EXPECT_EQ(kTestCommandCategoty, + GetPropertyValue<std::string>(dbus_constants::kCommandCategory)); + EXPECT_EQ(kTestCommandId, + GetPropertyValue<std::string>(dbus_constants::kCommandId)); + EXPECT_EQ(dbus_constants::kCommandStatusQueued, + GetPropertyValue<std::string>(dbus_constants::kCommandStatus)); + EXPECT_EQ(0, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress)); +} + +TEST_F(DBusCommandProxyTest, SetProgress) { + EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2); + auto response = CallMethod(dbus_constants::kCommandSetProgress, + [](dbus::MessageWriter* writer) { + writer->AppendInt32(10); + }); + VerifyResponse(response, {}); + EXPECT_EQ(dbus_constants::kCommandStatusInProgress, GetStatus()); + EXPECT_EQ(10, GetProgress()); + EXPECT_EQ(dbus_constants::kCommandStatusInProgress, + GetPropertyValue<std::string>(dbus_constants::kCommandStatus)); + EXPECT_EQ(10, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress)); +} + +TEST_F(DBusCommandProxyTest, SetProgress_OutOfRange) { + auto response = CallMethod(dbus_constants::kCommandSetProgress, + [](dbus::MessageWriter* writer) { + writer->AppendInt32(110); + }); + EXPECT_TRUE(IsResponseError(response)); + EXPECT_EQ(dbus_constants::kCommandStatusQueued, GetStatus()); + EXPECT_EQ(0, GetProgress()); +} + +TEST_F(DBusCommandProxyTest, Abort) { + EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); + auto response = CallMethod(dbus_constants::kCommandAbort, {}); + VerifyResponse(response, {}); + EXPECT_EQ(dbus_constants::kCommandStatusAborted, GetStatus()); +} + +TEST_F(DBusCommandProxyTest, Cancel) { + EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); + auto response = CallMethod(dbus_constants::kCommandCancel, {}); + VerifyResponse(response, {}); + EXPECT_EQ(dbus_constants::kCommandStatusCanceled, GetStatus()); +} + +TEST_F(DBusCommandProxyTest, Done) { + EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2); + auto response = CallMethod(dbus_constants::kCommandDone, {}); + VerifyResponse(response, {}); + EXPECT_EQ(dbus_constants::kCommandStatusDone, GetStatus()); + EXPECT_EQ(100, GetProgress()); +} + +} // namespace buffet
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc index 505dd5d..694d74e 100644 --- a/buffet/commands/prop_types.cc +++ b/buffet/commands/prop_types.cc
@@ -149,6 +149,13 @@ return val->FromJson(value, error) && ValidateConstraints(*val, error); } +bool PropType::ValidateValue(const Any& value, + chromeos::ErrorPtr* error) const { + std::shared_ptr<PropValue> val = CreateValue(value); + CHECK(val) << "Failed to create value object"; + return ValidateConstraints(*val, error); +} + bool PropType::ValidateConstraints(const PropValue& value, chromeos::ErrorPtr* error) const { for (const auto& pair : constraints_) {
diff --git a/buffet/commands/prop_types.h b/buffet/commands/prop_types.h index 7f2b2e6..506c822 100644 --- a/buffet/commands/prop_types.h +++ b/buffet/commands/prop_types.h
@@ -121,6 +121,9 @@ // the |error| parameter. bool ValidateValue(const base::Value* value, chromeos::ErrorPtr* error) const; + // Similar to the above method, but uses Any as the value container. + bool ValidateValue(const Any& value, chromeos::ErrorPtr* error) const; + // Additional helper static methods to help with converting a type enum // value into a string and back. using TypeMap = std::vector<std::pair<ValueType, std::string>>;
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc index d8c70ba..7fc0acf 100644 --- a/buffet/dbus_constants.cc +++ b/buffet/dbus_constants.cc
@@ -22,6 +22,29 @@ const char kManagerUpdateStateMethod[] = "UpdateState"; const char kManagerTestMethod[] = "TestMethod"; +const char kCommandInterface[] = "org.chromium.Buffet.Command"; +const char kCommandServicePathPrefix[] = "/org/chromium/Buffet/commands/"; + +const char kCommandSetProgress[] = "SetProgress"; +const char kCommandAbort[] = "Abort"; +const char kCommandCancel[] = "Cancel"; +const char kCommandDone[] = "Done"; + +const char kCommandName[] = "Name"; +const char kCommandCategory[] = "Category"; +const char kCommandId[] = "Id"; +const char kCommandStatus[] = "Status"; +const char kCommandProgress[] = "Progress"; + +const char kCommandStatusQueued[] = "queued"; +const char kCommandStatusInProgress[] = "inProgress"; +const char kCommandStatusPaused[] = "paused"; +const char kCommandStatusError[] = "error"; +const char kCommandStatusDone[] = "done"; +const char kCommandStatusCanceled[] = "canceled"; +const char kCommandStatusAborted[] = "aborted"; +const char kCommandStatusExpired[] = "expired"; + } // namespace dbus_constants } // namespace buffet
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h index b7e9239..aa6cf9f 100644 --- a/buffet/dbus_constants.h +++ b/buffet/dbus_constants.h
@@ -27,6 +27,33 @@ extern const char kManagerUpdateStateMethod[]; extern const char kManagerTestMethod[]; +// Interface implemented by the command instance objects. +extern const char kCommandInterface[]; +extern const char kCommandServicePathPrefix[]; + +// Methods exposed as part of kCommandInterface. +extern const char kCommandSetProgress[]; +extern const char kCommandAbort[]; +extern const char kCommandCancel[]; +extern const char kCommandDone[]; + +// Properties exposed as part of kCommandInterface. +extern const char kCommandName[]; +extern const char kCommandCategory[]; +extern const char kCommandId[]; +extern const char kCommandStatus[]; +extern const char kCommandProgress[]; + +// Values for command execution status. +extern const char kCommandStatusQueued[]; +extern const char kCommandStatusInProgress[]; +extern const char kCommandStatusPaused[]; +extern const char kCommandStatusError[]; +extern const char kCommandStatusDone[]; +extern const char kCommandStatusCanceled[]; +extern const char kCommandStatusAborted[]; +extern const char kCommandStatusExpired[]; + } // namespace dbus_constants } // namespace buffet