AU: Fall back to full updates after a number of failed delta update attempts.

Count each failed delta update attempt. Keep the count in persistent storage. If
the count exceeds a threshold (3), disable delta updates by setting delta_okay
to false in the update check request.

Once this CL is in, we have to ensure that we have a full update payload
available on the update server for each version otherwise we may orphan clients.

BUG=7221
TEST=unit tests, gmerged on device and tested with a mod'ed dev server

Change-Id: I7f7fa73f652f12f22cc8604dad6a26c802b8582d

Review URL: http://codereview.chromium.org/3617002
diff --git a/prefs.cc b/prefs.cc
index 7e98e79..585408c 100644
--- a/prefs.cc
+++ b/prefs.cc
@@ -4,16 +4,18 @@
 
 #include "update_engine/prefs.h"
 
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/string_number_conversions.h"
-#include "base/string_util.h"
+#include <base/file_util.h>
+#include <base/logging.h>
+#include <base/string_number_conversions.h>
+#include <base/string_util.h>
+
 #include "update_engine/utils.h"
 
 using std::string;
 
 namespace chromeos_update_engine {
 
+const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
 const char kPrefsLastActivePingDay[] = "last-active-ping-day";
 const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
 
diff --git a/prefs_interface.h b/prefs_interface.h
index dbfa68a..724651e 100644
--- a/prefs_interface.h
+++ b/prefs_interface.h
@@ -9,6 +9,7 @@
 
 namespace chromeos_update_engine {
 
+extern const char kPrefsDeltaUpdateFailures[];
 extern const char kPrefsLastActivePingDay[];
 extern const char kPrefsLastRollCallPingDay[];
 
diff --git a/update_attempter.cc b/update_attempter.cc
index 73b93b8..a9c9261 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -25,6 +25,7 @@
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response_handler_action.h"
 #include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs_interface.h"
 #include "update_engine/set_bootable_flag_action.h"
 #include "update_engine/update_check_scheduler.h"
 
@@ -36,6 +37,8 @@
 
 namespace chromeos_update_engine {
 
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
 const char* kUpdateCompletedMarker =
     "/var/run/update_engine_autoupdate_completed";
 
@@ -101,7 +104,8 @@
       download_progress_(0.0),
       last_checked_time_(0),
       new_version_("0.0.0.0"),
-      new_size_(0) {
+      new_size_(0),
+      is_full_update_(false) {
   if (utils::FileExists(kUpdateCompletedMarker))
     status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
 }
@@ -126,6 +130,7 @@
     LOG(ERROR) << "Unable to initialize Omaha request device params.";
     return;
   }
+  DisableDeltaUpdateIfNeeded();
   CHECK(!processor_->IsRunning());
   processor_->set_delegate(this);
 
@@ -251,6 +256,7 @@
   if (code == kActionCodeSuccess) {
     SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
     utils::WriteFile(kUpdateCompletedMarker, "", 0);
+    prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
 
     // Report the time it took to update the system.
     int64_t update_time = time(NULL) - last_checked_time_;
@@ -307,6 +313,14 @@
     }
   }
   if (code != kActionCodeSuccess) {
+    // If this was a delta update attempt and the current state is at or past
+    // the download phase, count the failure in case a switch to full update
+    // becomes necessary. Ignore network transfer timeouts and failures.
+    if (status_ >= UPDATE_STATUS_DOWNLOADING &&
+        !is_full_update_ &&
+        code != kActionCodeDownloadTransferError) {
+      MarkDeltaUpdateFailure();
+    }
     // On failure, schedule an error event to be sent to Omaha.
     CreatePendingErrorEvent(action, code);
     return;
@@ -326,6 +340,7 @@
     // TODO(adlr): put version in InstallPlan
     new_version_ = "0.0.0.0";
     new_size_ = plan.size;
+    is_full_update_ = plan.is_full_update;
     SetupPriorityManagement();
   } else if (type == DownloadAction::StaticType()) {
     SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
@@ -490,4 +505,24 @@
   return false;
 }
 
+void UpdateAttempter::DisableDeltaUpdateIfNeeded() {
+  int64_t delta_failures;
+  if (omaha_request_params_.delta_okay &&
+      prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) &&
+      delta_failures >= kMaxDeltaUpdateFailures) {
+    LOG(WARNING) << "Too many delta update failures, forcing full update.";
+    omaha_request_params_.delta_okay = false;
+  }
+}
+
+void UpdateAttempter::MarkDeltaUpdateFailure() {
+  CHECK(!is_full_update_);
+  int64_t delta_failures;
+  if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
+      delta_failures < 0) {
+    delta_failures = 0;
+  }
+  prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index 5df6af9..05f3dcb 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -45,6 +45,8 @@
 class UpdateAttempter : public ActionProcessorDelegate,
                         public DownloadActionDelegate {
  public:
+  static const int kMaxDeltaUpdateFailures;
+
   UpdateAttempter(PrefsInterface* prefs, MetricsLibraryInterface* metrics_lib);
   virtual ~UpdateAttempter();
 
@@ -106,6 +108,8 @@
 
  private:
   friend class UpdateAttempterTest;
+  FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+  FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
   FRIEND_TEST(UpdateAttempterTest, UpdateTest);
 
   // Sets the status to the given status and notifies a status update
@@ -142,6 +146,14 @@
   static gboolean StaticManagePriorityCallback(gpointer data);
   bool ManagePriorityCallback();
 
+  // Checks if a full update is needed and forces it by updating the Omaha
+  // request params.
+  void DisableDeltaUpdateIfNeeded();
+
+  // If this was a delta update attempt that failed, count it so that a full
+  // update can be tried when needed.
+  void MarkDeltaUpdateFailure();
+
   // 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.
@@ -188,6 +200,7 @@
   int64_t last_checked_time_;
   std::string new_version_;
   int64_t new_size_;
+  bool is_full_update_;
 
   // Device paramaters common to all Omaha requests.
   OmahaRequestDeviceParams omaha_request_params_;
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index ed58572..3c802b7 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -9,13 +9,17 @@
 #include "update_engine/action_processor_mock.h"
 #include "update_engine/filesystem_copier_action.h"
 #include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs_mock.h"
 #include "update_engine/set_bootable_flag_action.h"
 #include "update_engine/update_attempter.h"
 
 using std::string;
+using testing::_;
+using testing::DoAll;
 using testing::InSequence;
 using testing::Property;
 using testing::Return;
+using testing::SetArgumentPointee;
 
 namespace chromeos_update_engine {
 
@@ -44,12 +48,15 @@
     EXPECT_EQ(0, attempter_.last_checked_time_);
     EXPECT_EQ("0.0.0.0", attempter_.new_version_);
     EXPECT_EQ(0, attempter_.new_size_);
+    EXPECT_FALSE(attempter_.is_full_update_);
     processor_ = new ActionProcessorMock();
     attempter_.processor_.reset(processor_);  // Transfers ownership.
+    attempter_.prefs_ = &prefs_;
   }
 
   UpdateAttempterUnderTest attempter_;
   ActionProcessorMock* processor_;
+  PrefsMock prefs_;
 };
 
 TEST_F(UpdateAttempterTest, RunAsRootConstructWithUpdatedMarkerTest) {
@@ -92,6 +99,47 @@
             GetErrorCodeForAction(&action_mock, kActionCodeError));
 }
 
+TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) {
+  attempter_.omaha_request_params_.delta_okay = true;
+  EXPECT_CALL(prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(Return(false));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_TRUE(attempter_.omaha_request_params_.delta_okay);
+  EXPECT_CALL(prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+          Return(true)));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_TRUE(attempter_.omaha_request_params_.delta_okay);
+  EXPECT_CALL(prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+          Return(true)));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_FALSE(attempter_.omaha_request_params_.delta_okay);
+  EXPECT_CALL(prefs_, GetInt64(_, _)).Times(0);
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_FALSE(attempter_.omaha_request_params_.delta_okay);
+}
+
+TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) {
+  attempter_.is_full_update_ = false;
+  EXPECT_CALL(prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(Return(false))
+      .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(true)))
+      .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(true)))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+          Return(true)));
+  EXPECT_CALL(prefs_, SetInt64(kPrefsDeltaUpdateFailures, 1)).Times(2);
+  EXPECT_CALL(prefs_, SetInt64(kPrefsDeltaUpdateFailures, 2)).Times(1);
+  EXPECT_CALL(prefs_, SetInt64(kPrefsDeltaUpdateFailures,
+                               UpdateAttempter::kMaxDeltaUpdateFailures + 1))
+      .Times(1);
+  for (int i = 0; i < 4; i ++)
+    attempter_.MarkDeltaUpdateFailure();
+}
+
 TEST_F(UpdateAttempterTest, UpdateStatusToStringTest) {
   extern const char* UpdateStatusToString(UpdateStatus);
   EXPECT_STREQ("UPDATE_STATUS_IDLE", UpdateStatusToString(UPDATE_STATUS_IDLE));