buffet: Implement exponential backoff for cloud requests

Simplified logic of DoCloudRequest() method in buffet to de-couple
the multiple levels of callbacks and retries. Also added support for
exponential backoff on request failures.

In the process made RefreshAccessToken and GetDeviceInfo methods
fully asynchronous.

BUG=brillo:955
TEST=`FEATURES=test emerge-link buffet`
     `test_that -b link 100.96.49.59 e:buffet_.*`

Change-Id: Ieeb2fa42ea25f15841bad5c6c09c6c9990f96943
Reviewed-on: https://chromium-review.googlesource.com/280833
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
index c791355..0bbaeb1 100644
--- a/buffet/device_registration_info_unittest.cc
+++ b/buffet/device_registration_info_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <base/json/json_reader.h>
 #include <base/json/json_writer.h>
+#include <base/message_loop/message_loop.h>
+#include <base/run_loop.h>
 #include <base/values.h>
 #include <chromeos/bind_lambda.h>
 #include <chromeos/http/http_request.h>
@@ -200,8 +202,28 @@
     return dev_reg_->PublishCommands(commands);
   }
 
-  bool CheckRegistration(chromeos::ErrorPtr* error) const {
-    return dev_reg_->CheckRegistration(error);
+  bool RefreshAccessToken(chromeos::ErrorPtr* error) const {
+    base::MessageLoopForIO message_loop;
+    base::RunLoop run_loop;
+
+    bool succeeded = false;
+    auto on_success = [&run_loop, &succeeded]() {
+      succeeded = true;
+      run_loop.Quit();
+    };
+    auto on_failure = [&run_loop, &error](const chromeos::Error* in_error) {
+      if (error)
+        *error = in_error->Clone();
+      run_loop.Quit();
+    };
+    dev_reg_->RefreshAccessToken(base::Bind(on_success),
+                                 base::Bind(on_failure));
+    run_loop.Run();
+    return succeeded;
+  }
+
+  void SetAccessToken() {
+    dev_reg_->access_token_ = test_data::kAccessToken;
   }
 
   RegistrationStatus GetRegistrationStatus() const {
@@ -252,8 +274,8 @@
   }));
 }
 
-TEST_F(DeviceRegistrationInfoTest, CheckRegistration) {
-  EXPECT_FALSE(CheckRegistration(nullptr));
+TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
+  EXPECT_FALSE(dev_reg_->HaveRegistrationCredentials(nullptr));
   EXPECT_EQ(0, transport_->GetRequestCount());
 
   SetDefaultDeviceRegistration(&data_);
@@ -264,8 +286,9 @@
                          chromeos::http::request_type::kPost,
                          base::Bind(OAuth2Handler));
   transport_->ResetRequestCount();
-  EXPECT_TRUE(CheckRegistration(nullptr));
+  EXPECT_TRUE(RefreshAccessToken(nullptr));
   EXPECT_EQ(1, transport_->GetRequestCount());
+  EXPECT_TRUE(dev_reg_->HaveRegistrationCredentials(nullptr));
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
@@ -279,7 +302,7 @@
                          base::Bind(OAuth2HandlerFail));
   transport_->ResetRequestCount();
   chromeos::ErrorPtr error;
-  EXPECT_FALSE(CheckRegistration(&error));
+  EXPECT_FALSE(RefreshAccessToken(&error));
   EXPECT_EQ(1, transport_->GetRequestCount());
   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "unable_to_authenticate"));
   EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
@@ -296,7 +319,7 @@
                          base::Bind(OAuth2HandlerDeregister));
   transport_->ResetRequestCount();
   chromeos::ErrorPtr error;
-  EXPECT_FALSE(CheckRegistration(&error));
+  EXPECT_FALSE(RefreshAccessToken(&error));
   EXPECT_EQ(1, transport_->GetRequestCount());
   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant"));
   EXPECT_EQ(RegistrationStatus::kInvalidCredentials, GetRegistrationStatus());
@@ -306,36 +329,32 @@
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
   ReloadConfig();
+  SetAccessToken();
 
-  transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
-                         chromeos::http::request_type::kPost,
-                         base::Bind(OAuth2Handler));
   transport_->AddHandler(dev_reg_->GetDeviceURL(),
                          chromeos::http::request_type::kGet,
                          base::Bind(DeviceInfoHandler));
   transport_->ResetRequestCount();
-  auto device_info = dev_reg_->GetDeviceInfo(nullptr);
-  EXPECT_EQ(2, transport_->GetRequestCount());
-  EXPECT_NE(nullptr, device_info.get());
-  base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(device_info->GetAsDictionary(&dict));
-  std::string id;
-  EXPECT_TRUE(dict->GetString("id", &id));
-  EXPECT_EQ(test_data::kDeviceId, id);
-}
+  base::MessageLoopForIO message_loop;
+  base::RunLoop run_loop;
 
-TEST_F(DeviceRegistrationInfoTest, GetDeviceId) {
-  SetDefaultDeviceRegistration(&data_);
-  storage_->Save(data_);
-  ReloadConfig();
-
-  transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
-                         chromeos::http::request_type::kPost,
-                         base::Bind(OAuth2Handler));
-  transport_->AddHandler(dev_reg_->GetDeviceURL(),
-                         chromeos::http::request_type::kGet,
-                         base::Bind(DeviceInfoHandler));
-  EXPECT_EQ(test_data::kDeviceId, config_->device_id());
+  bool succeeded = false;
+  auto on_success =
+      [&run_loop, &succeeded, this](const base::DictionaryValue& info) {
+    EXPECT_EQ(1, transport_->GetRequestCount());
+    std::string id;
+    EXPECT_TRUE(info.GetString("id", &id));
+    EXPECT_EQ(test_data::kDeviceId, id);
+    succeeded = true;
+    run_loop.Quit();
+  };
+  auto on_failure = [&run_loop](const chromeos::Error* error) {
+    run_loop.Quit();
+    FAIL() << "Should not be called";
+  };
+  dev_reg_->GetDeviceInfo(base::Bind(on_success), base::Bind(on_failure));
+  run_loop.Run();
+  EXPECT_TRUE(succeeded);
 }
 
 TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
@@ -483,6 +502,11 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, UpdateCommand) {
+  SetDefaultDeviceRegistration(&data_);
+  storage_->Save(data_);
+  ReloadConfig();
+  SetAccessToken();
+
   auto json_cmds = unittests::CreateDictionaryValue(R"({
     'robot': {
       '_jump': {