update_engine: Add staging to update_attempter and omaha_request

Modify update_attempter and omaha_request so that they take the user's
staging settings into account. This includes:
* Turning off scattering if staging is on.
* Preserving scattering values if staging is off.
* Setting staging related persisted values if staging is on.
* Deleting the corresponding persisted values if staging is off.
* Making omaha use the max wait time for staging as its max scatter
period if staging is on.
* Add the corresponding unittests for this.

BUG=chromium:858621
TEST=cros_workon_make update_engine --test

Change-Id: I501f90d9e195b5ceef61d3eac67ab52051793ae9
Reviewed-on: https://chromium-review.googlesource.com/1141217
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Adolfo Higueros <adokar@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index e08a868..c9bc269 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -1481,13 +1481,18 @@
       system_state_->clock()->GetWallclockTime() - update_first_seen_at;
   TimeDelta max_scatter_period =
       TimeDelta::FromDays(output_object->max_days_to_scatter);
+  int64_t staging_wait_time_in_days = 0;
+  // Use staging and its default max value if staging is on.
+  if (system_state_->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod,
+                                       &staging_wait_time_in_days) &&
+      staging_wait_time_in_days > 0)
+    max_scatter_period = TimeDelta::FromDays(kMaxWaitTimeStagingInDays);
 
   LOG(INFO) << "Waiting Period = "
             << utils::FormatSecs(params_->waiting_period().InSeconds())
             << ", Time Elapsed = "
             << utils::FormatSecs(elapsed_time.InSeconds())
-            << ", MaxDaysToScatter = "
-            << max_scatter_period.InDays();
+            << ", MaxDaysToScatter = " << max_scatter_period.InDays();
 
   if (!output_object->deadline.empty()) {
     // The deadline is set for all rules which serve a delta update from a
diff --git a/omaha_request_action.h b/omaha_request_action.h
index c3c8233..1034c3f 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -127,6 +127,10 @@
   // fallback ones.
   static const int kDefaultMaxFailureCountPerUrl = 10;
 
+  // If staging is enabled, set the maximum wait time to 28 days, since that is
+  // the predetermined wait time for staging.
+  static const int kMaxWaitTimeStagingInDays = 28;
+
   // These are the possible outcome upon checking whether we satisfied
   // the wall-clock-based-wait.
   enum WallClockWaitResult {
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 63bb170..1e0ad6d 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -1336,6 +1336,42 @@
   EXPECT_TRUE(response.update_exists);
 }
 
+TEST_F(OmahaRequestActionTest, StagingTurnedOnCausesScattering) {
+  // If staging is on, the value for max days to scatter should be ignored, and
+  // staging's scatter value should be used.
+  OmahaResponse response;
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta::FromDays(6));
+  request_params_.set_update_check_count_wait_enabled(false);
+
+  ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, 6));
+  // This should not prevent scattering due to staging.
+  fake_update_response_.max_days_to_scatter = "0";
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kDeferring,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+
+  // Interactive updates should not be affected.
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
 TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
   OmahaResponse response;
   fake_update_response_.include_cohorts = true;
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 62cbdab..c8e26b5 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -311,14 +311,14 @@
   // Whether the client is accepting rollback images too.
   bool rollback_allowed_;
 
-  // True if scattering is enabled, in which case waiting_period_ specifies the
-  // amount of absolute time that we've to wait for before sending a request to
-  // Omaha.
+  // True if scattering or staging are enabled, in which case waiting_period_
+  // specifies the amount of absolute time that we've to wait for before sending
+  // a request to Omaha.
   bool wall_clock_based_wait_enabled_;
   base::TimeDelta waiting_period_;
 
-  // True if scattering is enabled to denote the number of update checks
-  // we've to skip before we can send a request to Omaha. The min and max
+  // True if scattering or staging are enabled to denote the number of update
+  // checks we've to skip before we can send a request to Omaha. The min and max
   // values establish the bounds for a random number to be chosen within that
   // range to enable such a wait.
   bool update_check_count_wait_enabled_;
diff --git a/update_attempter.cc b/update_attempter.cc
index 517b0a8..6a34aed 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -76,6 +76,8 @@
 using chromeos_update_manager::EvalStatus;
 using chromeos_update_manager::Policy;
 using chromeos_update_manager::UpdateCheckParams;
+using chromeos_update_manager::CalculateStagingCase;
+using chromeos_update_manager::StagingCase;
 using std::set;
 using std::shared_ptr;
 using std::string;
@@ -373,7 +375,11 @@
   // Set whether rollback is allowed.
   omaha_request_params_->set_rollback_allowed(rollback_allowed);
 
-  CalculateScatteringParams(interactive);
+  CalculateStagingParams(interactive);
+  // If staging_wait_time_ wasn't set, staging is off, use scattering instead.
+  if (staging_wait_time_.InSeconds() == 0) {
+    CalculateScatteringParams(interactive);
+  }
 
   CalculateP2PParams(interactive);
   if (payload_state->GetUsingP2PForDownloading() ||
@@ -580,6 +586,45 @@
       omaha_request_params_->waiting_period());
 }
 
+void UpdateAttempter::CalculateStagingParams(bool interactive) {
+  bool oobe_complete = system_state_->hardware()->IsOOBEEnabled() &&
+                       system_state_->hardware()->IsOOBEComplete(nullptr);
+  auto device_policy = system_state_->device_policy();
+  StagingCase staging_case = StagingCase::kOff;
+  if (device_policy && !interactive && oobe_complete) {
+    staging_wait_time_ = omaha_request_params_->waiting_period();
+    staging_case = CalculateStagingCase(
+        device_policy, prefs_, &staging_wait_time_, &staging_schedule_);
+  }
+  switch (staging_case) {
+    case StagingCase::kOff:
+      // Staging is off, get rid of persisted value.
+      prefs_->Delete(kPrefsWallClockStagingWaitPeriod);
+      // Set |staging_wait_time_| to its default value so scattering can still
+      // be turned on
+      staging_wait_time_ = TimeDelta();
+      break;
+    // Let the cases fall through since they just add, and never remove, steps
+    // to turning staging on.
+    case StagingCase::kNoSavedValue:
+      prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod,
+                       staging_wait_time_.InDays());
+    case StagingCase::kSetStagingFromPref:
+      omaha_request_params_->set_waiting_period(staging_wait_time_);
+    case StagingCase::kNoAction:
+      // Staging is on, enable wallclock based wait so that its values get used.
+      omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+      // Use UpdateCheckCount if possible to prevent devices updating all at
+      // once.
+      omaha_request_params_->set_update_check_count_wait_enabled(
+          DecrementUpdateCheckCount());
+      // Scattering should not be turned on if staging is on, delete the
+      // existing scattering configuration.
+      prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+      scatter_factor_ = TimeDelta();
+  }
+}
+
 void UpdateAttempter::BuildUpdateActions(bool interactive) {
   CHECK(!processor_->IsRunning());
   processor_->set_delegate(this);
@@ -955,6 +1000,7 @@
     // way.
     prefs_->Delete(kPrefsUpdateCheckCount);
     system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta());
+    system_state_->payload_state()->SetStagingWaitPeriod(TimeDelta());
     prefs_->Delete(kPrefsUpdateFirstSeenAt);
 
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
diff --git a/update_attempter.h b/update_attempter.h
index a405abc..108a6c6 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -44,6 +44,7 @@
 #include "update_engine/service_observer_interface.h"
 #include "update_engine/system_state.h"
 #include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/staging_utils.h"
 #include "update_engine/update_manager/update_manager.h"
 
 namespace policy {
@@ -410,6 +411,8 @@
   // Returns whether an update is currently running or scheduled.
   bool IsUpdateRunningOrScheduled();
 
+  void CalculateStagingParams(bool interactive);
+
   // Last status notification timestamp used for throttling. Use monotonic
   // TimeTicks to ensure that notifications are sent even if the system clock is
   // set back in the middle of an update.
@@ -510,6 +513,10 @@
   std::string forced_app_version_;
   std::string forced_omaha_url_;
 
+  // If this is not TimeDelta(), then that means staging is turned on.
+  base::TimeDelta staging_wait_time_;
+  chromeos_update_manager::StagingSchedule staging_schedule_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
 };
 
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 2a9e3e6..93fcef1 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -54,7 +54,9 @@
 using base::Time;
 using base::TimeDelta;
 using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::StagingSchedule;
 using chromeos_update_manager::UpdateCheckParams;
+using policy::DevicePolicy;
 using std::string;
 using std::unique_ptr;
 using testing::_;
@@ -175,6 +177,12 @@
   void ResetRollbackHappenedStart(bool is_consumer,
                                   bool is_policy_available,
                                   bool expected_reset);
+  // Staging related callbacks.
+  void SetUpStagingTest(const StagingSchedule& schedule, FakePrefs* prefs);
+  void CheckStagingOff();
+  void StagingSetsPrefsAndTurnsOffScatteringStart();
+  void StagingOffIfInteractiveStart();
+  void StagingOffIfOobeStart();
 
   bool actual_using_p2p_for_downloading() {
     return actual_using_p2p_for_downloading_;
@@ -459,6 +467,9 @@
   PostinstallRunnerAction::StaticType(),
 };
 
+const StagingSchedule kValidStagingSchedule = {
+    {4, 10}, {10, 40}, {19, 70}, {26, 100}};
+
 }  // namespace
 
 void UpdateAttempterTest::UpdateTestStart() {
@@ -931,6 +942,125 @@
   ScheduleQuitMainLoop();
 }
 
+void UpdateAttempterTest::SetUpStagingTest(const StagingSchedule& schedule,
+                                           FakePrefs* prefs) {
+  attempter_.prefs_ = prefs;
+  fake_system_state_.set_prefs(prefs);
+
+  int64_t initial_value = 8;
+  EXPECT_TRUE(
+      prefs->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value));
+  EXPECT_TRUE(prefs->SetInt64(kPrefsUpdateCheckCount, initial_value));
+  attempter_.scatter_factor_ = TimeDelta::FromSeconds(20);
+
+  auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy.get());
+  EXPECT_CALL(*device_policy, GetDeviceUpdateStagingSchedule(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(schedule), Return(true)));
+
+  attempter_.policy_provider_.reset(
+      new policy::PolicyProvider(std::move(device_policy)));
+}
+
+TEST_F(UpdateAttempterTest, StagingSetsPrefsAndTurnsOffScattering) {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(
+          &UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart,
+          base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart() {
+  // Tests that staging sets its prefs properly and turns off scattering.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, false);
+  // Check that prefs have the correct values.
+  int64_t update_count;
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count));
+  int64_t waiting_time_days;
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsWallClockStagingWaitPeriod,
+                                  &waiting_time_days));
+  EXPECT_GT(waiting_time_days, 0);
+  // Update count should have been decremented.
+  EXPECT_EQ(7, update_count);
+  // Check that Omaha parameters were updated correctly.
+  EXPECT_TRUE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+  EXPECT_TRUE(
+      attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+  EXPECT_EQ(waiting_time_days,
+            attempter_.omaha_request_params_->waiting_period().InDays());
+  // Check class variables.
+  EXPECT_EQ(waiting_time_days, attempter_.staging_wait_time_.InDays());
+  EXPECT_EQ(kValidStagingSchedule, attempter_.staging_schedule_);
+  // Check that scattering is turned off
+  EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod));
+
+  ScheduleQuitMainLoop();
+}
+
+void UpdateAttempterTest::CheckStagingOff() {
+  // Check that all prefs were removed.
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsUpdateCheckCount));
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockScatteringWaitPeriod));
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockStagingWaitPeriod));
+  // Check that the Omaha parameters have the correct value.
+  EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InDays());
+  EXPECT_EQ(attempter_.omaha_request_params_->waiting_period(),
+            attempter_.staging_wait_time_);
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+  // Check that scattering is turned off too.
+  EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfInteractive) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::StagingOffIfInteractiveStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfInteractiveStart() {
+  // Tests that staging is turned off when an interactive update is requested.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, /* interactive = */ true);
+  CheckStagingOff();
+
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfOobe) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::StagingOffIfOobeStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfOobeStart() {
+  // Tests that staging is turned off if OOBE hasn't been completed.
+  fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true);
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, /* interactive = */ true);
+  CheckStagingOff();
+
+  ScheduleQuitMainLoop();
+}
+
 // Checks that we only report daily metrics at most every 24 hours.
 TEST_F(UpdateAttempterTest, ReportDailyMetrics) {
   FakeClock fake_clock;