buffet: Introduce a helper method to do cloud API request.

For now this helper is somewhat naive and it requires a lot of
love to reach production quality.

Eventually it will go away from device_registration_info.

BUG=chromium:420580
TEST=cros_workon_make buffet --test && manual testing.

Change-Id: Iada36af6ea6b0be4c5af6c318818e3ad0682625e
Reviewed-on: https://chromium-review.googlesource.com/221075
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Anton Muhin <antonm@chromium.org>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index 1e4d511..2c04dcf 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -9,7 +9,9 @@
 #include <vector>
 
 #include <base/json/json_writer.h>
+#include <base/message_loop/message_loop.h>
 #include <base/values.h>
+#include <chromeos/bind_lambda.h>
 #include <chromeos/data_encoding.h>
 #include <chromeos/http/http_utils.h>
 #include <chromeos/mime_utils.h>
@@ -338,6 +340,7 @@
   if (!CheckRegistration(error))
     return std::unique_ptr<base::Value>();
 
+  // TODO(antonm): Switch to DoCloudRequest later.
   auto response = chromeos::http::Get(
       GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
   int status_code = 0;
@@ -483,6 +486,72 @@
   return true;
 }
 
+void DeviceRegistrationInfo::DoCloudRequest(
+    const char* method,
+    const std::string& url,
+    const base::DictionaryValue* body,
+    CloudRequestCallback callback,
+    CloudRequestErroback errorback) {
+  // TODO(antonm): Add retries for 5xx.
+  // TODO(antonm): Add reauthorisation on access token expiration (do not
+  // forget about 5xx when fetching new access token).
+  // TODO(antonm): Add support for device removal.
+
+  chromeos::ErrorPtr error;
+
+  auto report_error = [errorback, &error] () {
+    auto cb = [errorback] (chromeos::Error* error_ptr) {
+      errorback.Run(*error_ptr);
+    };
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE, base::Bind(cb, base::Owned(error.release())));
+  };
+
+  std::string data;
+  if (body)
+    base::JSONWriter::Write(body, &data);
+
+  const std::string mime_type{chromeos::mime::AppendParameter(
+      chromeos::mime::application::kJson,
+      chromeos::mime::parameters::kCharset,
+      "utf-8")};
+
+  const std::unique_ptr<const chromeos::http::Response> response{
+      chromeos::http::SendRequest(
+          method,
+          url,
+          data.c_str(), data.size(),
+          mime_type.c_str(),
+          {GetAuthorizationHeader()},
+          transport_,
+          &error)};
+  if (!response) {
+    report_error();
+    return;
+  }
+
+  int status_code{0};
+  std::unique_ptr<base::DictionaryValue> json_resp{
+      chromeos::http::ParseJsonResponse(response.get(), &status_code, &error)};
+  if (!json_resp) {
+    report_error();
+    return;
+  }
+
+  if (status_code >= chromeos::http::status_code::BadRequest) {
+    LOG(WARNING) << "Cloud request failed. Response code = " << status_code;
+    ParseGCDError(json_resp.get(), &error);
+    report_error();
+    return;
+  }
+
+  auto cb = [callback] (base::DictionaryValue* result) {
+    callback.Run(*result);
+  };
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE, base::Bind(cb, base::Owned(json_resp.release())));
+}
+
 void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {
   if (!CheckRegistration(error))
     return;
@@ -492,18 +561,13 @@
   if (!device_resource)
     return;
 
-  // TODO(antonm): Use PUT, not PATCH for updates.
-  std::unique_ptr<chromeos::http::Response> response =
-      chromeos::http::PatchJson(GetDeviceURL(), device_resource.release(),
-                                {GetAuthorizationHeader()}, transport_, error);
-  if (!response)
-    return;
+  DoCloudRequest(
+      chromeos::http::request_type::kPut,
+      GetDeviceURL(),
+      device_resource.release(),
+      base::Bind([](const base::DictionaryValue& data) {}),
+      base::Bind([](const chromeos::Error& error) {}));
 
-  int status_code = 0;
-  std::unique_ptr<base::DictionaryValue> json =
-      chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
-  if (!json || status_code >= chromeos::http::status_code::BadRequest)
-    return;
 
   // TODO(antonm): Implement the rest of startup sequence:
   //   * Abort commands cloud thinks are running
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
index 6a82ffa..8292ea6 100644
--- a/buffet/device_registration_info.h
+++ b/buffet/device_registration_info.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <utility>
 
+#include <base/callback.h>
 #include <base/macros.h>
 #include <base/time/time.h>
 #include <chromeos/data_encoding.h>
@@ -120,6 +121,23 @@
   // Makes sure the access token is available and up-to-date.
   bool ValidateAndRefreshAccessToken(chromeos::ErrorPtr* error);
 
+  using CloudRequestCallback =
+      base::Callback<void(const base::DictionaryValue&)>;
+  using CloudRequestErroback =
+      base::Callback<void(const chromeos::Error& error)>;
+
+  // Do a HTTPS request to cloud services.
+  // Handles many cases like reauthorization, 5xx HTTP response codes
+  // and device removal.  It is a recommended way to do cloud API
+  // requests.
+  // TODO(antonm): Consider moving into some other class.
+  void DoCloudRequest(
+      const char* method,
+      const std::string& url,
+      const base::DictionaryValue* body,
+      CloudRequestCallback callback,
+      CloudRequestErroback errorback);
+
   // Builds Cloud API devices collection REST resouce which matches
   // current state of the device including command definitions
   // for all supported commands and current device state.