buffet: Report command instance parsing error to cloud server

When a command comes from GCD server and fails to parse, we used to
just skip the command without reporting anything to the server.

This lead to the same command coming down in the next fetch cycle and
be retried over and over again.

Now if buffet cannot process a command, we mark it as "aborted" on the
server and provide actual failure reason - in the "error" property of
the command resource.

BUG=brillo:952
TEST=`FEATURES=test emerge-buffet`
     Tested manually on device through GCD dev site.

Change-Id: Idcda5ca296696a01d7e008f148f125c1a4ed1072
Reviewed-on: https://chromium-review.googlesource.com/268442
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/commands/command_instance_unittest.cc b/buffet/commands/command_instance_unittest.cc
index 8d7d0ec..f7744b0 100644
--- a/buffet/commands/command_instance_unittest.cc
+++ b/buffet/commands/command_instance_unittest.cc
@@ -108,14 +108,18 @@
 TEST_F(CommandInstanceTest, FromJson) {
   auto json = CreateDictionaryValue(R"({
     'name': 'robot.jump',
+    'id': 'abcd',
     'parameters': {
       'height': 53,
       '_jumpType': '_withKick'
     },
     'results': {}
   })");
+  std::string id;
   auto instance =
-      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr);
+      CommandInstance::FromJson(json.get(), "cloud", dict_, &id, nullptr);
+  EXPECT_EQ("abcd", id);
+  EXPECT_EQ("abcd", instance->GetID());
   EXPECT_EQ("robot.jump", instance->GetName());
   EXPECT_EQ("robotd", instance->GetCategory());
   EXPECT_EQ(53, instance->FindParameter("height")->GetInt()->GetValue());
@@ -126,7 +130,7 @@
 TEST_F(CommandInstanceTest, FromJson_ParamsOmitted) {
   auto json = CreateDictionaryValue("{'name': 'base.reboot'}");
   auto instance =
-      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr);
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, nullptr);
   EXPECT_EQ("base.reboot", instance->GetName());
   EXPECT_EQ("robotd", instance->GetCategory());
   EXPECT_TRUE(instance->GetParameters().empty());
@@ -135,7 +139,8 @@
 TEST_F(CommandInstanceTest, FromJson_NotObject) {
   auto json = CreateValue("'string'");
   chromeos::ErrorPtr error;
-  auto instance = CommandInstance::FromJson(json.get(), "cloud", dict_, &error);
+  auto instance =
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, &error);
   EXPECT_EQ(nullptr, instance.get());
   EXPECT_EQ("json_object_expected", error->GetCode());
   EXPECT_EQ("Command instance is not a JSON object", error->GetMessage());
@@ -144,7 +149,8 @@
 TEST_F(CommandInstanceTest, FromJson_NameMissing) {
   auto json = CreateDictionaryValue("{'param': 'value'}");
   chromeos::ErrorPtr error;
-  auto instance = CommandInstance::FromJson(json.get(), "cloud", dict_, &error);
+  auto instance =
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, &error);
   EXPECT_EQ(nullptr, instance.get());
   EXPECT_EQ("parameter_missing", error->GetCode());
   EXPECT_EQ("Command name is missing", error->GetMessage());
@@ -153,7 +159,8 @@
 TEST_F(CommandInstanceTest, FromJson_UnknownCommand) {
   auto json = CreateDictionaryValue("{'name': 'robot.scream'}");
   chromeos::ErrorPtr error;
-  auto instance = CommandInstance::FromJson(json.get(), "cloud", dict_, &error);
+  auto instance =
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, &error);
   EXPECT_EQ(nullptr, instance.get());
   EXPECT_EQ("invalid_command_name", error->GetCode());
   EXPECT_EQ("Unknown command received: robot.scream", error->GetMessage());
@@ -165,7 +172,8 @@
     'parameters': 'hello'
   })");
   chromeos::ErrorPtr error;
-  auto instance = CommandInstance::FromJson(json.get(), "cloud", dict_, &error);
+  auto instance =
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, &error);
   EXPECT_EQ(nullptr, instance.get());
   auto inner = error->GetInnerError();
   EXPECT_EQ("json_object_expected", inner->GetCode());
@@ -183,7 +191,8 @@
     }
   })");
   chromeos::ErrorPtr error;
-  auto instance = CommandInstance::FromJson(json.get(), "cloud", dict_, &error);
+  auto instance =
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, &error);
   EXPECT_EQ(nullptr, instance.get());
   auto first = error->GetFirstError();
   EXPECT_EQ("out_of_range", first->GetCode());
@@ -207,7 +216,7 @@
     'results': {}
   })");
   auto instance =
-      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr);
+      CommandInstance::FromJson(json.get(), "cloud", dict_, nullptr, nullptr);
   instance->SetProgress(
       native_types::Object{{"progress", unittests::make_int_prop_value(15)}});
   instance->SetProgress(