Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-32078

Creating collection with specific UUID using applyOps can roll back a pending collection drop

    • Type: Icon: Bug Bug
    • Resolution: Duplicate
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: None
    • Component/s: Replication
    • Replication
    • ALL
    • Hide
      
      load("jstests/libs/check_log.js");  // For 'checkLog'.
      
      // Pause oplog application on a specified node.
      function pauseOplogApplication(node) {
          assert.commandWorked(
              node.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"}));
          checkLog.contains(node, "rsSyncApplyStop fail point enabled");
      }
      
      // Resume oplog application on a specified node.
      function resumeOplogApplication(node) {
          assert.commandWorked(node.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"}));
      }
      
      var replTest = new ReplSetTest({name: "applyOpsUUID", nodes: 2});
      
      replTest.startSet();
      replTest.initiate();
      replTest.awaitReplication();
      
      // Pause application on secondary so collection drop doesn't commit.
      pauseOplogApplication(replTest.getSecondary());
      
      // Get connections and collection.
      let primary = replTest.getPrimary();
      let pdb = primary.getDB("test");
      let collName = "coll";
      
      // Create collection.
      assert.commandWorked(pdb.createCollection(collName));
      
      // Get the UUID for 'coll'.
      let uuid = pdb.getCollectionInfos()[0].info.uuid;
      
      jsTestLog("Dropping collection.");
      assert.commandWorked(pdb.runCommand({drop: collName}));
      assert.commandWorked(pdb.createCollection("otherColl"));
      
      // Create collection with UUID via applyOps.
      let ops = [{
          "op": "c",
          "ns": "test.$cmd",
          "ui": uuid,
          "o": {
              "create": collName,
              "idIndex": {"v": 2, "key": {"_id": 1}, "name": "_id_", "ns": "test.coll"}
          }
      }];
      jsTestLog("Doing 'applyOps' command.");
      assert.commandWorked(pdb.adminCommand({applyOps: ops}));
      
      // Dump oplog of primary.
      jsTestLog("Primary oplog.");
      let oplog = primary.getDB("local")["oplog.rs"].find().toArray();
      jsTestLog(tojson(oplog));
      
      jsTestLog("Resuming oplog application.");
      resumeOplogApplication(replTest.getSecondary());
      
      // Check data consistency.
      replTest.awaitReplication();
      replTest.checkReplicatedDataHashes();
      
      Show
      load( "jstests/libs/check_log.js" ); // For 'checkLog' . // Pause oplog application on a specified node. function pauseOplogApplication(node) { assert.commandWorked( node.adminCommand({configureFailPoint: "rsSyncApplyStop" , mode: "alwaysOn" })); checkLog.contains(node, "rsSyncApplyStop fail point enabled" ); } // Resume oplog application on a specified node. function resumeOplogApplication(node) { assert.commandWorked(node.adminCommand({configureFailPoint: "rsSyncApplyStop" , mode: "off" })); } var replTest = new ReplSetTest({name: "applyOpsUUID" , nodes: 2}); replTest.startSet(); replTest.initiate(); replTest.awaitReplication(); // Pause application on secondary so collection drop doesn't commit. pauseOplogApplication(replTest.getSecondary()); // Get connections and collection. let primary = replTest.getPrimary(); let pdb = primary.getDB( "test" ); let collName = "coll" ; // Create collection. assert.commandWorked(pdb.createCollection(collName)); // Get the UUID for 'coll' . let uuid = pdb.getCollectionInfos()[0].info.uuid; jsTestLog( "Dropping collection." ); assert.commandWorked(pdb.runCommand({drop: collName})); assert.commandWorked(pdb.createCollection( "otherColl" )); // Create collection with UUID via applyOps. let ops = [{ "op" : "c" , "ns" : "test.$cmd" , "ui" : uuid, "o" : { "create" : collName, "idIndex" : { "v" : 2, "key" : { "_id" : 1}, "name" : "_id_" , "ns" : "test.coll" } } }]; jsTestLog( "Doing 'applyOps' command." ); assert.commandWorked(pdb.adminCommand({applyOps: ops})); // Dump oplog of primary. jsTestLog( "Primary oplog." ); let oplog = primary.getDB( "local" )[ "oplog.rs" ].find().toArray(); jsTestLog(tojson(oplog)); jsTestLog( "Resuming oplog application." ); resumeOplogApplication(replTest.getSecondary()); // Check data consistency. replTest.awaitReplication(); replTest.checkReplicatedDataHashes();
    • 0

      If a collection on a primary is in "drop-pending" state, by executing a create collection command via the applyOps command using the original UUID of that collection it is possible to erroneously "roll back" that collection i.e. remove it from the "drop-pending" state, and make it a normal collection again. Presumably, the correct thing to do would be to disallow collection creation operations via applyOps on collection UUIDs that are currently in drop-pending state.

      This issue can lead to a db hash mismatch between a primary and secondary, since the drop-pending "roll back" gets logged in the oplog as a renameCollection operation. When a secondary applies the initial collection drop (the drop prepare), it might physically drop the collection before it applies the later renameCollection operation from the primary. If it tries to then apply the renameCollection operation on a collection that has been physically dropped, it will presumably fail silently, leaving it permanently missing a collection that the primary still has. See repro script.

            Assignee:
            backlog-server-repl [DO NOT USE] Backlog - Replication Team
            Reporter:
            william.schultz@mongodb.com William Schultz (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: