tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_node_reassignment.js (15718B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 _(
      5  "Test that node reassignment responses are respected on all kinds of " +
      6    "requests."
      7 );
      8 
      9 const { RESTRequest } = ChromeUtils.importESModule(
     10  "resource://services-common/rest.sys.mjs"
     11 );
     12 const { Service } = ChromeUtils.importESModule(
     13  "resource://services-sync/service.sys.mjs"
     14 );
     15 
     16 add_task(async function setup() {
     17  validate_all_future_pings();
     18 });
     19 
     20 /**
     21 * Emulate the following Zeus config:
     22 * $draining = data.get($prefix . $host . " draining");
     23 * if ($draining == "drain.") {
     24 *   log.warn($log_host_db_status . " migrating=1 (node-reassignment)" .
     25 *            $log_suffix);
     26 *   http.sendResponse("401 Node reassignment", $content_type,
     27 *                     '"server request: node reassignment"', "");
     28 * }
     29 */
     30 const reassignBody = '"server request: node reassignment"';
     31 
     32 // API-compatible with SyncServer handler. Bind `handler` to something to use
     33 // as a ServerCollection handler.
     34 function handleReassign(handler, req, resp) {
     35  resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
     36  resp.setHeader("Content-Type", "application/json");
     37  resp.bodyOutputStream.write(reassignBody, reassignBody.length);
     38 }
     39 
     40 async function prepareServer() {
     41  let server = new SyncServer();
     42  server.registerUser("johndoe");
     43  server.start();
     44  syncTestLogging();
     45  await configureIdentity({ username: "johndoe" }, server);
     46  return server;
     47 }
     48 
     49 function getReassigned() {
     50  try {
     51    return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
     52  } catch (ex) {
     53    if (ex.result != Cr.NS_ERROR_UNEXPECTED) {
     54      do_throw(
     55        "Got exception retrieving lastSyncReassigned: " + Log.exceptionStr(ex)
     56      );
     57    }
     58  }
     59  return false;
     60 }
     61 
     62 /**
     63 * Make a test request to `url`, then watch the result of two syncs
     64 * to ensure that a node request was made.
     65 * Runs `between` between the two. This can be used to undo deliberate failure
     66 * setup, detach observers, etc.
     67 */
     68 async function syncAndExpectNodeReassignment(
     69  server,
     70  firstNotification,
     71  between,
     72  secondNotification,
     73  url
     74 ) {
     75  let deferred = Promise.withResolvers();
     76 
     77  let getTokenCount = 0;
     78  let mockTSC = {
     79    // TokenServerClient
     80    async getTokenUsingOAuth() {
     81      getTokenCount++;
     82      return { endpoint: server.baseURI + "1.1/johndoe/" };
     83    },
     84  };
     85  Service.identity._tokenServerClient = mockTSC;
     86 
     87  // Make sure that it works!
     88  let request = new RESTRequest(url);
     89  let response = await request.get();
     90  Assert.equal(response.status, 401);
     91 
     92  function onFirstSync() {
     93    _("First sync completed.");
     94    Svc.Obs.remove(firstNotification, onFirstSync);
     95    Svc.Obs.add(secondNotification, onSecondSync);
     96 
     97    Assert.equal(Service.clusterURL, "");
     98 
     99    // Allow for tests to clean up error conditions.
    100    between();
    101  }
    102  function onSecondSync() {
    103    _("Second sync completed.");
    104    Svc.Obs.remove(secondNotification, onSecondSync);
    105    Service.scheduler.clearSyncTriggers();
    106 
    107    // Make absolutely sure that any event listeners are done with their work
    108    // before we proceed.
    109    waitForZeroTimer(function () {
    110      _("Second sync nextTick.");
    111      Assert.equal(getTokenCount, 1);
    112      Service.startOver().then(() => {
    113        server.stop(deferred.resolve);
    114      });
    115    });
    116  }
    117 
    118  Svc.Obs.add(firstNotification, onFirstSync);
    119  await Service.sync();
    120 
    121  await deferred.promise;
    122 }
    123 
    124 add_task(async function test_momentary_401_engine() {
    125  enableValidationPrefs();
    126 
    127  _("Test a failure for engine URLs that's resolved by reassignment.");
    128  let server = await prepareServer();
    129  let john = server.user("johndoe");
    130 
    131  _("Enabling the Rotary engine.");
    132  let { engine, syncID, tracker } = await registerRotaryEngine();
    133 
    134  // We need the server to be correctly set up prior to experimenting. Do this
    135  // through a sync.
    136  let global = {
    137    syncID: Service.syncID,
    138    storageVersion: STORAGE_VERSION,
    139    rotary: { version: engine.version, syncID },
    140  };
    141  john.createCollection("meta").insert("global", global);
    142 
    143  _("First sync to prepare server contents.");
    144  await Service.sync();
    145 
    146  let numResets = 0;
    147  let observeReset = (obs, topic) => {
    148    if (topic == "rotary") {
    149      numResets += 1;
    150    }
    151  };
    152  _("Adding observer that we saw an engine reset.");
    153  Svc.Obs.add("weave:engine:reset-client:finish", observeReset);
    154 
    155  _("Setting up Rotary collection to 401.");
    156  let rotary = john.createCollection("rotary");
    157  let oldHandler = rotary.collectionHandler;
    158  rotary.collectionHandler = handleReassign.bind(this, undefined);
    159 
    160  // We want to verify that the clusterURL pref has been cleared after a 401
    161  // inside a sync. Flag the Rotary engine to need syncing.
    162  john.collection("rotary").timestamp += 1000;
    163 
    164  function between() {
    165    _("Undoing test changes.");
    166    rotary.collectionHandler = oldHandler;
    167 
    168    function onLoginStart() {
    169      // lastSyncReassigned shouldn't be cleared until a sync has succeeded.
    170      _("Ensuring that lastSyncReassigned is still set at next sync start.");
    171      Svc.Obs.remove("weave:service:login:start", onLoginStart);
    172      Assert.ok(getReassigned());
    173    }
    174 
    175    _("Adding observer that lastSyncReassigned is still set on login.");
    176    Svc.Obs.add("weave:service:login:start", onLoginStart);
    177  }
    178 
    179  await syncAndExpectNodeReassignment(
    180    server,
    181    "weave:service:sync:finish",
    182    between,
    183    "weave:service:sync:finish",
    184    Service.storageURL + "rotary"
    185  );
    186 
    187  Svc.Obs.remove("weave:engine:reset-client:finish", observeReset);
    188  Assert.equal(numResets, 1);
    189  await tracker.clearChangedIDs();
    190  await Service.engineManager.unregister(engine);
    191 });
    192 
    193 // This test ends up being a failing fetch *after we're already logged in*.
    194 add_task(async function test_momentary_401_info_collections() {
    195  enableValidationPrefs();
    196 
    197  _("Test a failure for info/collections that's resolved by reassignment.");
    198  let server = await prepareServer();
    199 
    200  _("First sync to prepare server contents.");
    201  await Service.sync();
    202 
    203  // Return a 401 for info requests, particularly info/collections.
    204  let oldHandler = server.toplevelHandlers.info;
    205  server.toplevelHandlers.info = handleReassign;
    206 
    207  function undo() {
    208    _("Undoing test changes.");
    209    server.toplevelHandlers.info = oldHandler;
    210  }
    211 
    212  await syncAndExpectNodeReassignment(
    213    server,
    214    "weave:service:sync:error",
    215    undo,
    216    "weave:service:sync:finish",
    217    Service.infoURL
    218  );
    219 });
    220 
    221 add_task(async function test_momentary_401_storage_loggedin() {
    222  enableValidationPrefs();
    223 
    224  _(
    225    "Test a failure for any storage URL, not just engine parts. " +
    226      "Resolved by reassignment."
    227  );
    228  let server = await prepareServer();
    229 
    230  _("Performing initial sync to ensure we are logged in.");
    231  await Service.sync();
    232 
    233  // Return a 401 for all storage requests.
    234  let oldHandler = server.toplevelHandlers.storage;
    235  server.toplevelHandlers.storage = handleReassign;
    236 
    237  function undo() {
    238    _("Undoing test changes.");
    239    server.toplevelHandlers.storage = oldHandler;
    240  }
    241 
    242  Assert.ok(Service.isLoggedIn, "already logged in");
    243  await syncAndExpectNodeReassignment(
    244    server,
    245    "weave:service:sync:error",
    246    undo,
    247    "weave:service:sync:finish",
    248    Service.storageURL + "meta/global"
    249  );
    250 });
    251 
    252 add_task(async function test_momentary_401_storage_loggedout() {
    253  enableValidationPrefs();
    254 
    255  _(
    256    "Test a failure for any storage URL, not just engine parts. " +
    257      "Resolved by reassignment."
    258  );
    259  let server = await prepareServer();
    260 
    261  // Return a 401 for all storage requests.
    262  let oldHandler = server.toplevelHandlers.storage;
    263  server.toplevelHandlers.storage = handleReassign;
    264 
    265  function undo() {
    266    _("Undoing test changes.");
    267    server.toplevelHandlers.storage = oldHandler;
    268  }
    269 
    270  Assert.ok(!Service.isLoggedIn, "not already logged in");
    271  await syncAndExpectNodeReassignment(
    272    server,
    273    "weave:service:login:error",
    274    undo,
    275    "weave:service:sync:finish",
    276    Service.storageURL + "meta/global"
    277  );
    278 });
    279 
    280 add_task(async function test_loop_avoidance_storage() {
    281  enableValidationPrefs();
    282 
    283  _(
    284    "Test that a repeated failure doesn't result in a sync loop " +
    285      "if node reassignment cannot resolve the failure."
    286  );
    287 
    288  let server = await prepareServer();
    289 
    290  // Return a 401 for all storage requests.
    291  let oldHandler = server.toplevelHandlers.storage;
    292  server.toplevelHandlers.storage = handleReassign;
    293 
    294  let firstNotification = "weave:service:login:error";
    295  let secondNotification = "weave:service:login:error";
    296  let thirdNotification = "weave:service:sync:finish";
    297 
    298  let deferred = Promise.withResolvers();
    299 
    300  let getTokenCount = 0;
    301  let mockTSC = {
    302    // TokenServerClient
    303    async getTokenUsingOAuth() {
    304      getTokenCount++;
    305      return { endpoint: server.baseURI + "1.1/johndoe/" };
    306    },
    307  };
    308  Service.identity._tokenServerClient = mockTSC;
    309 
    310  // Track the time. We want to make sure the duration between the first and
    311  // second sync is small, and then that the duration between second and third
    312  // is set to be large.
    313  let now;
    314 
    315  function onFirstSync() {
    316    _("First sync completed.");
    317    Svc.Obs.remove(firstNotification, onFirstSync);
    318    Svc.Obs.add(secondNotification, onSecondSync);
    319 
    320    Assert.equal(Service.clusterURL, "");
    321 
    322    // We got a 401 mid-sync, and set the pref accordingly.
    323    Assert.ok(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
    324 
    325    // Update the timestamp.
    326    now = Date.now();
    327  }
    328 
    329  function onSecondSync() {
    330    _("Second sync completed.");
    331    Svc.Obs.remove(secondNotification, onSecondSync);
    332    Svc.Obs.add(thirdNotification, onThirdSync);
    333 
    334    // This sync occurred within the backoff interval.
    335    let elapsedTime = Date.now() - now;
    336    Assert.less(elapsedTime, MINIMUM_BACKOFF_INTERVAL);
    337 
    338    // This pref will be true until a sync completes successfully.
    339    Assert.ok(getReassigned());
    340 
    341    // The timer will be set for some distant time.
    342    // We store nextSync in prefs, which offers us only limited resolution.
    343    // Include that logic here.
    344    let expectedNextSync =
    345      1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
    346    _("Next sync scheduled for " + Service.scheduler.nextSync);
    347    _("Expected to be slightly greater than " + expectedNextSync);
    348 
    349    Assert.greaterOrEqual(Service.scheduler.nextSync, expectedNextSync);
    350    Assert.ok(!!Service.scheduler.syncTimer);
    351 
    352    // Undo our evil scheme.
    353    server.toplevelHandlers.storage = oldHandler;
    354 
    355    // Bring the timer forward to kick off a successful sync, so we can watch
    356    // the pref get cleared.
    357    Service.scheduler.scheduleNextSync(0);
    358  }
    359  function onThirdSync() {
    360    Svc.Obs.remove(thirdNotification, onThirdSync);
    361 
    362    // That'll do for now; no more syncs.
    363    Service.scheduler.clearSyncTriggers();
    364 
    365    // Make absolutely sure that any event listeners are done with their work
    366    // before we proceed.
    367    waitForZeroTimer(function () {
    368      _("Third sync nextTick.");
    369      Assert.ok(!getReassigned());
    370      Assert.equal(getTokenCount, 2);
    371      Service.startOver().then(() => {
    372        server.stop(deferred.resolve);
    373      });
    374    });
    375  }
    376 
    377  Svc.Obs.add(firstNotification, onFirstSync);
    378 
    379  now = Date.now();
    380  await Service.sync();
    381  await deferred.promise;
    382 });
    383 
    384 add_task(async function test_loop_avoidance_engine() {
    385  enableValidationPrefs();
    386 
    387  _(
    388    "Test that a repeated 401 in an engine doesn't result in a sync loop " +
    389      "if node reassignment cannot resolve the failure."
    390  );
    391  let server = await prepareServer();
    392  let john = server.user("johndoe");
    393 
    394  _("Enabling the Rotary engine.");
    395  let { engine, syncID, tracker } = await registerRotaryEngine();
    396  let deferred = Promise.withResolvers();
    397 
    398  let getTokenCount = 0;
    399  let mockTSC = {
    400    // TokenServerClient
    401    async getTokenUsingOAuth() {
    402      getTokenCount++;
    403      return { endpoint: server.baseURI + "1.1/johndoe/" };
    404    },
    405  };
    406  Service.identity._tokenServerClient = mockTSC;
    407 
    408  // We need the server to be correctly set up prior to experimenting. Do this
    409  // through a sync.
    410  let global = {
    411    syncID: Service.syncID,
    412    storageVersion: STORAGE_VERSION,
    413    rotary: { version: engine.version, syncID },
    414  };
    415  john.createCollection("meta").insert("global", global);
    416 
    417  _("First sync to prepare server contents.");
    418  await Service.sync();
    419 
    420  _("Setting up Rotary collection to 401.");
    421  let rotary = john.createCollection("rotary");
    422  let oldHandler = rotary.collectionHandler;
    423  rotary.collectionHandler = handleReassign.bind(this, undefined);
    424 
    425  // Flag the Rotary engine to need syncing.
    426  john.collection("rotary").timestamp += 1000;
    427 
    428  function onLoginStart() {
    429    // lastSyncReassigned shouldn't be cleared until a sync has succeeded.
    430    _("Ensuring that lastSyncReassigned is still set at next sync start.");
    431    Assert.ok(getReassigned());
    432  }
    433 
    434  function beforeSuccessfulSync() {
    435    _("Undoing test changes.");
    436    rotary.collectionHandler = oldHandler;
    437  }
    438 
    439  let firstNotification = "weave:service:sync:finish";
    440  let secondNotification = "weave:service:sync:finish";
    441  let thirdNotification = "weave:service:sync:finish";
    442 
    443  // Track the time. We want to make sure the duration between the first and
    444  // second sync is small, and then that the duration between second and third
    445  // is set to be large.
    446  let now;
    447 
    448  function onFirstSync() {
    449    _("First sync completed.");
    450    Svc.Obs.remove(firstNotification, onFirstSync);
    451    Svc.Obs.add(secondNotification, onSecondSync);
    452 
    453    Assert.equal(Service.clusterURL, "");
    454 
    455    _("Adding observer that lastSyncReassigned is still set on login.");
    456    Svc.Obs.add("weave:service:login:start", onLoginStart);
    457 
    458    // We got a 401 mid-sync, and set the pref accordingly.
    459    Assert.ok(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
    460 
    461    // Update the timestamp.
    462    now = Date.now();
    463  }
    464 
    465  function onSecondSync() {
    466    _("Second sync completed.");
    467    Svc.Obs.remove(secondNotification, onSecondSync);
    468    Svc.Obs.add(thirdNotification, onThirdSync);
    469 
    470    // This sync occurred within the backoff interval.
    471    let elapsedTime = Date.now() - now;
    472    Assert.less(elapsedTime, MINIMUM_BACKOFF_INTERVAL);
    473 
    474    // This pref will be true until a sync completes successfully.
    475    Assert.ok(getReassigned());
    476 
    477    // The timer will be set for some distant time.
    478    // We store nextSync in prefs, which offers us only limited resolution.
    479    // Include that logic here.
    480    let expectedNextSync =
    481      1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
    482    _("Next sync scheduled for " + Service.scheduler.nextSync);
    483    _("Expected to be slightly greater than " + expectedNextSync);
    484 
    485    Assert.greaterOrEqual(Service.scheduler.nextSync, expectedNextSync);
    486    Assert.ok(!!Service.scheduler.syncTimer);
    487 
    488    // Undo our evil scheme.
    489    beforeSuccessfulSync();
    490 
    491    // Bring the timer forward to kick off a successful sync, so we can watch
    492    // the pref get cleared.
    493    Service.scheduler.scheduleNextSync(0);
    494  }
    495 
    496  function onThirdSync() {
    497    Svc.Obs.remove(thirdNotification, onThirdSync);
    498 
    499    // That'll do for now; no more syncs.
    500    Service.scheduler.clearSyncTriggers();
    501 
    502    // Make absolutely sure that any event listeners are done with their work
    503    // before we proceed.
    504    waitForZeroTimer(function () {
    505      _("Third sync nextTick.");
    506      Assert.ok(!getReassigned());
    507      Assert.equal(getTokenCount, 2);
    508      Svc.Obs.remove("weave:service:login:start", onLoginStart);
    509      Service.startOver().then(() => {
    510        server.stop(deferred.resolve);
    511      });
    512    });
    513  }
    514 
    515  Svc.Obs.add(firstNotification, onFirstSync);
    516 
    517  now = Date.now();
    518  await Service.sync();
    519  await deferred.promise;
    520 
    521  await tracker.clearChangedIDs();
    522  await Service.engineManager.unregister(engine);
    523 });