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/device_registration_info.cc b/buffet/device_registration_info.cc
index a56a15a..eec9985 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -53,6 +53,7 @@
 
 const int kMaxStartDeviceRetryDelayMinutes{1};
 const int64_t kMinStartDeviceRetryDelaySeconds{5};
+const int64_t kAbortCommandRetryDelaySeconds{5};
 
 std::pair<std::string, std::string> BuildAuthHeader(
     const std::string& access_token_type,
@@ -789,6 +790,45 @@
       base::Bind(&IgnoreCloudErrorWithCallback, on_error));
 }
 
+void DeviceRegistrationInfo::NotifyCommandAborted(
+    const std::string& command_id,
+    chromeos::ErrorPtr error) {
+  base::DictionaryValue command_patch;
+  command_patch.SetString(commands::attributes::kCommand_State,
+                          CommandInstance::kStatusAborted);
+  if (error) {
+    command_patch.SetString(commands::attributes::kCommand_ErrorCode,
+                            chromeos::string_utils::Join(":",
+                                                         error->GetDomain(),
+                                                         error->GetCode()));
+    std::vector<std::string> messages;
+    const chromeos::Error* current_error = error.get();
+    while (current_error) {
+      messages.push_back(current_error->GetMessage());
+      current_error = current_error->GetInnerError();
+    }
+    command_patch.SetString(commands::attributes::kCommand_ErrorMessage,
+                            chromeos::string_utils::Join(";", messages));
+  }
+  UpdateCommand(command_id,
+                command_patch,
+                base::Bind(&base::DoNothing),
+                base::Bind(&DeviceRegistrationInfo::RetryNotifyCommandAborted,
+                           weak_factory_.GetWeakPtr(),
+                           command_id, base::Passed(std::move(error))));
+}
+
+void DeviceRegistrationInfo::RetryNotifyCommandAborted(
+    const std::string& command_id,
+    chromeos::ErrorPtr error) {
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&DeviceRegistrationInfo::NotifyCommandAborted,
+                 weak_factory_.GetWeakPtr(),
+                 command_id, base::Passed(std::move(error))),
+      base::TimeDelta::FromSeconds(kAbortCommandRetryDelaySeconds));
+}
+
 void DeviceRegistrationInfo::UpdateDeviceResource(
     const base::Closure& on_success,
     const CloudRequestErrorCallback& on_failure) {
@@ -903,11 +943,15 @@
       continue;
     }
 
+    std::string command_id;
+    chromeos::ErrorPtr error;
     auto command_instance = CommandInstance::FromJson(
         command, commands::attributes::kCommand_Visibility_Cloud,
-        command_dictionary, nullptr);
+        command_dictionary, &command_id, &error);
     if (!command_instance) {
-      LOG(WARNING) << "Failed to parse a command";
+      LOG(WARNING) << "Failed to parse a command with ID: " << command_id;
+      if (!command_id.empty())
+        NotifyCommandAborted(command_id, std::move(error));
       continue;
     }