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