Bug 578195 - Better description when the server asks for a backoff. r=philikon
authorMarina Samuel <msamuel@mozilla.com>
Fri, 26 Aug 2011 16:45:08 -0700
changeset 76311 c3d9c66fb073b2bf9f8c90675fb9f591345e4235
parent 76310 84c0f187733508d7c22869c7af4b58d94ba45826
child 76312 82446dae2f5a465f52374581dcee5bf3977eaee0
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersphilikon
bugs578195
milestone9.0a1
Bug 578195 - Better description when the server asks for a backoff. r=philikon
services/sync/locales/en-US/errors.properties
services/sync/modules/constants.js
services/sync/modules/policies.js
services/sync/tests/unit/test_errorhandler.js
services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
--- a/services/sync/locales/en-US/errors.properties
+++ b/services/sync/locales/en-US/errors.properties
@@ -1,17 +1,18 @@
 error.login.reason.network      = Failed to connect to the server
 error.login.reason.recoverykey  = Wrong Recovery Key
 error.login.reason.account      = Incorrect account name or password
 error.login.reason.no_username  = Missing account name
 error.login.reason.no_password2 = Missing password
 error.login.reason.no_recoverykey= No saved Recovery Key to use
 error.login.reason.server       = Server incorrectly configured
 
-error.sync.failed_partial     = One or more data types could not be synced
+error.sync.failed_partial            = One or more data types could not be synced
+error.sync.reason.server_maintenance = Firefox Sync server maintenance is underway, syncing will resume automatically.
 
 invalid-captcha = Incorrect words, try again
 weak-password   = Use a stronger password
 
 # this is the fallback, if we hit an error we didn't bother to localize
 error.reason.unknown          = Unknown error
 
 change.password.pwSameAsRecoveryKey  = Password can't match your Recovery Key
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -158,16 +158,17 @@ METARECORD_DOWNLOAD_FAIL:              "
 VERSION_OUT_OF_DATE:                   "error.sync.reason.version_out_of_date",
 DESKTOP_VERSION_OUT_OF_DATE:           "error.sync.reason.desktop_version_out_of_date",
 SETUP_FAILED_NO_PASSPHRASE:            "error.sync.reason.setup_failed_no_passphrase",
 CREDENTIALS_CHANGED:                   "error.sync.reason.credentials_changed",
 ABORT_SYNC_COMMAND:                    "aborting sync, process commands said so",
 NO_SYNC_NODE_FOUND:                    "error.sync.reason.no_node_found",
 OVER_QUOTA:                            "error.sync.reason.over_quota",
 PROLONGED_SYNC_FAILURE:                "error.sync.prolonged_failure",
+SERVER_MAINTENANCE:                    "error.sync.reason.server_maintenance",
 
 RESPONSE_OVER_QUOTA:                   "14",
 
 // engine failure status codes
 ENGINE_UPLOAD_FAIL:                    "error.engine.reason.record_upload_fail",
 ENGINE_DOWNLOAD_FAIL:                  "error.engine.reason.record_download_fail",
 ENGINE_UNKNOWN_FAIL:                   "error.engine.reason.unknown_fail",
 ENGINE_APPLY_FAIL:                     "error.engine.reason.apply_fail",
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -509,31 +509,31 @@ let ErrorHandler = {
           Svc.Obs.notify("weave:ui:sync:error");
         } else {
           Svc.Obs.notify("weave:ui:sync:finish");
         }
 
         this.dontIgnoreErrors = false;
         break;
       case "weave:service:sync:finish":
-        this.dontIgnoreErrors = false;
-
         if (Status.service == SYNC_FAILED_PARTIAL) {
           this._log.debug("Some engines did not sync correctly.");
           this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
                             LOG_PREFIX_ERROR);
 
           if (this.shouldReportError()) {
+            this.dontIgnoreErrors = false;
             Svc.Obs.notify("weave:ui:sync:error");
             break;
           }
         } else {
           this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnSuccess"),
                             LOG_PREFIX_SUCCESS);
         }
+        this.dontIgnoreErrors = false;
         Svc.Obs.notify("weave:ui:sync:finish");
         break;
     }
   },
 
   /**
    * Trigger a sync and don't muffle any errors, particularly network errors.
    */
@@ -605,24 +605,28 @@ let ErrorHandler = {
     }
   },
 
   shouldReportError: function shouldReportError() {
     if (Status.login == MASTER_PASSWORD_LOCKED) {
       return false;
     }
 
+    if (this.dontIgnoreErrors) {
+      return true;
+    }
+
     let lastSync = Svc.Prefs.get("lastSync");
     if (lastSync && ((Date.now() - Date.parse(lastSync)) >
         Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
       Status.sync = PROLONGED_SYNC_FAILURE;
       return true;
     }
 
-    return (this.dontIgnoreErrors ||
+    return (Status.sync != SERVER_MAINTENANCE &&
             [Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
   },
 
   /**
    * Handle HTTP response results or exceptions and set the appropriate
    * Status.* bits.
    */
   checkServerError: function checkServerError(resp) {
@@ -639,16 +643,17 @@ let ErrorHandler = {
         break;
 
       case 500:
       case 502:
       case 503:
       case 504:
         Status.enforceBackoff = true;
         if (resp.status == 503 && resp.headers["retry-after"]) {
+          Status.sync = SERVER_MAINTENANCE;
           Svc.Obs.notify("weave:service:backoff:interval",
                          parseInt(resp.headers["retry-after"], 10));
         }
         break;
     }
 
     switch (resp.result) {
       case Cr.NS_ERROR_UNKNOWN_HOST:
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -25,17 +25,17 @@ function setLastSync(lastSyncValue) {
 }
 
 function CatapultEngine() {
   SyncEngine.call(this, "Catapult");
 }
 CatapultEngine.prototype = {
   __proto__: SyncEngine.prototype,
   exception: null, // tests fill this in
-  sync: function sync() {
+  _sync: function _sync() {
     throw this.exception;
   }
 };
 
 Engines.register(CatapultEngine);
 
 function run_test() {
   initTestLogging("Trace");
@@ -263,16 +263,45 @@ add_test(function test_shouldReportError
 
   // Test network, non-prolonged, sync error reported
   Status.resetSync();
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   ErrorHandler.dontIgnoreErrors = false;
   Status.sync = LOGIN_FAILED_NETWORK_ERROR;
   do_check_false(ErrorHandler.shouldReportError());
 
+  // Test server maintenance errors are not reported
+  Status.resetSync();
+  setLastSync(NON_PROLONGED_ERROR_DURATION);
+  ErrorHandler.dontIgnoreErrors = false;
+  Status.sync = SERVER_MAINTENANCE;
+  do_check_false(ErrorHandler.shouldReportError());
+
+  // Test prolonged server maintenance errors are reported
+  Status.resetSync();
+  setLastSync(PROLONGED_ERROR_DURATION);
+  ErrorHandler.dontIgnoreErrors = false;
+  Status.sync = SERVER_MAINTENANCE;
+  do_check_true(ErrorHandler.shouldReportError());
+
+  // Test dontIgnoreErrors, server maintenance errors are reported
+  Status.resetSync();
+  setLastSync(NON_PROLONGED_ERROR_DURATION);
+  ErrorHandler.dontIgnoreErrors = true;
+  Status.sync = SERVER_MAINTENANCE;
+  do_check_true(ErrorHandler.shouldReportError());
+
+  // Test dontIgnoreErrors, prolonged, server maintenance
+  // errors are reported
+  Status.resetSync();
+  setLastSync(PROLONGED_ERROR_DURATION);
+  ErrorHandler.dontIgnoreErrors = true;
+  Status.sync = SERVER_MAINTENANCE;
+  do_check_true(ErrorHandler.shouldReportError());
+
   run_next_test();
 });
 
 add_test(function test_shouldReportError_master_password() {
   _("Test error ignored due to locked master password");
   let server = sync_httpd_setup();
   setUp();
 
@@ -342,17 +371,17 @@ add_test(function test_login_syncAndRepo
   // Test prolonged, non-network errors are
   // reported when calling syncAndReportErrors.
   let server = sync_httpd_setup();
   setUp();
   Service.password = "";
 
   Svc.Obs.add("weave:ui:login:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:login:error", onSyncError);
-    do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
+    do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD);
 
     Service.startOver();
     server.stop(run_next_test);
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   ErrorHandler.syncAndReportErrors();
 });
@@ -367,17 +396,17 @@ add_test(function test_sync_syncAndRepor
   Service.sync();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
 
   generateCredentialsChangedFailure();
 
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
-    do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
+    do_check_eq(Status.sync, CREDENTIALS_CHANGED);
 
     Service.startOver();
     server.stop(run_next_test);
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   ErrorHandler.syncAndReportErrors();
 });
@@ -424,34 +453,34 @@ add_test(function test_login_syncAndRepo
   // when calling syncAndReportErrors.
   Service.username = "johndoe";
   Service.password = "ilovejane";
   Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
   Service.clusterURL = "http://localhost:8080/";
 
   Svc.Obs.add("weave:ui:login:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:login:error", onSyncError);
-    do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
+    do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
 
     Service.startOver();
     run_next_test();
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   ErrorHandler.syncAndReportErrors();
 });
 
 add_test(function test_sync_syncAndReportErrors_prolonged_network_error() {
   // Test prolonged, network errors are reported
   // when calling syncAndReportErrors.
   Services.io.offline = true;
 
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
-    do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
+    do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
 
     Services.io.offline = false;
     Service.startOver();
     run_next_test();
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   ErrorHandler.syncAndReportErrors();
@@ -593,16 +622,17 @@ add_test(function test_login_network_err
     Svc.Obs.remove("weave:service:login:error", onUIUpdate);
 
     // Wait until other login:error observers are called since
     // it may change Status.sync.
     Utils.nextTick(function() {
       do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
 
       Service.startOver();
+      Status.resetSync();
       Services.io.offline = false;
       run_next_test();
     });
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   Service.sync();
 });
@@ -620,16 +650,130 @@ add_test(function test_sync_network_erro
     Status.resetSync();
     run_next_test();
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   Service.sync();
 });
 
+add_test(function test_sync_server_maintenance_error() {
+  // Test server maintenance errors are not reported.
+  let server = sync_httpd_setup();
+  setUp();
+
+  const BACKOFF = 42;
+  let engine = Engines.get("catapult");
+  engine.enabled = true;
+  engine.exception = {status: 503,
+                      headers: {"retry-after": BACKOFF}};
+
+  function onUIUpdate() {
+    do_throw("Shouldn't get here!");
+  }
+  Svc.Obs.add("weave:ui:sync:error", onUIUpdate);
+
+  do_check_eq(Status.service, STATUS_OK);
+
+  // Last sync was 2 days ago
+  Svc.Prefs.set("lastSync", (new Date(Date.now() - 172800000)).toString());
+  Service.sync();
+
+  do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
+  do_check_eq(Status.sync, SERVER_MAINTENANCE);
+
+  Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
+  Service.startOver();
+  Status.resetSync();
+  server.stop(run_next_test);
+});
+
+add_test(function test_sync_prolonged_server_maintenance_error() {
+  // Test prolonged server maintenance errors are reported.
+  let server = sync_httpd_setup();
+  setUp();
+
+  const BACKOFF = 42;
+  let engine = Engines.get("catapult");
+  engine.enabled = true;
+  engine.exception = {status: 503,
+                      headers: {"retry-after": BACKOFF}};
+
+  Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
+    Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
+    do_check_eq(Status.service, SYNC_FAILED);
+    do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
+
+    Service.startOver();
+    Status.resetSync();
+    server.stop(run_next_test);
+  });
+
+  do_check_eq(Status.service, STATUS_OK);
+
+  setLastSync(PROLONGED_ERROR_DURATION);
+  Service.sync();
+});
+
+add_test(function test_syncAndReportErrors_server_maintenance_error() {
+  // Test server maintenance errors are reported
+  // when calling syncAndReportErrors.
+  let server = sync_httpd_setup();
+  setUp();
+
+  const BACKOFF = 42;
+  let engine = Engines.get("catapult");
+  engine.enabled = true;
+  engine.exception = {status: 503,
+                      headers: {"retry-after": BACKOFF}};
+
+  Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
+    Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
+    do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
+    do_check_eq(Status.sync, SERVER_MAINTENANCE);
+
+    Service.startOver();
+    Status.resetSync();
+    server.stop(run_next_test);
+  });
+
+  do_check_eq(Status.service, STATUS_OK);
+
+  setLastSync(NON_PROLONGED_ERROR_DURATION);
+  ErrorHandler.syncAndReportErrors();
+});
+
+add_test(function test_syncAndReportErrors_prolonged_server_maintenance_error() {
+  // Test prolonged server maintenance errors are
+  // reported when calling syncAndReportErrors.
+  let server = sync_httpd_setup();
+  setUp();
+
+  const BACKOFF = 42;
+  let engine = Engines.get("catapult");
+  engine.enabled = true;
+  engine.exception = {status: 503,
+                      headers: {"retry-after": BACKOFF}};
+
+  Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
+    Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
+    do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
+    do_check_eq(Status.sync, SERVER_MAINTENANCE);
+
+    Service.startOver();
+    Status.resetSync();
+    server.stop(run_next_test);
+  });
+
+  do_check_eq(Status.service, STATUS_OK);
+
+  setLastSync(PROLONGED_ERROR_DURATION);
+  ErrorHandler.syncAndReportErrors();
+});
+
 add_test(function test_sync_engine_generic_fail() {
   let server = sync_httpd_setup();
 
   let engine = Engines.get("catapult");
   engine.enabled = true;
   engine.sync = function sync() {
     Svc.Obs.notify("weave:engine:sync:error", "", "steam");
   };
--- a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
+++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
@@ -76,16 +76,17 @@ add_test(function test_backoff500() {
     do_check_false(Status.enforceBackoff);
 
     // Forcibly create and upload keys here -- otherwise we don't get to the 500!
     do_check_true(generateAndUploadKeys());
 
     Service.login();
     Service.sync();
     do_check_true(Status.enforceBackoff);
+    do_check_eq(Status.sync, SYNC_SUCCEEDED);
     do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
   } finally {
     Status.resetBackoff();
     Service.startOver();
   }
   server.stop(run_next_test);
 });
 
@@ -111,18 +112,20 @@ add_test(function test_backoff503() {
     do_check_true(generateAndUploadKeys());
 
     Service.login();
     Service.sync();
 
     do_check_true(Status.enforceBackoff);
     do_check_eq(backoffInterval, BACKOFF);
     do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
+    do_check_eq(Status.sync, SERVER_MAINTENANCE);
   } finally {
     Status.resetBackoff();
+    Status.resetSync();
     Service.startOver();
   }
   server.stop(run_next_test);
 });
 
 add_test(function test_overQuota() {
   _("Test: HTTP 400 with body error code 14 means over quota.");
   setUp();