Files
mongo/jstests/multiVersion/upgrade_downgrade_mongod.js
2015-10-01 08:00:10 -04:00

615 lines
22 KiB
JavaScript

/**
* Test the upgrade/downgrade process for the last stable release <~~> the latest release
* with the same storage engine (and options), via mongod binary swap.
* New features/changes tested in 3.2:
* - Partial index
* This sets partialFilterExpression attribute for an index.
* After downgrade, mongod should start up, but may miss documents when using the partial index
* to answer queries.
* - Document validation
* This sets the validator attribute for a collection.
* After downgrade, this should be ignored.
* - Text index
* This sets the text index version to a new value.
* After downgrade, mongod should fail to start (SERVER-19557).
* - Geo index
* This sets the geo 2dsphere index version to a new value.
* After downgrade, mongod should fail to start.
*/
(function() {
"use strict";
function waitForPrimary(db, timeout, interval) {
var timeStart = new Date().getTime();
timeout = timeout || 60000;
interval = interval || 500;
while (!db.isMaster().ismaster) {
sleep(interval);
var timeNow = new Date().getTime();
if (timeNow-timeStart > timeout) {
return 0;
}
}
return 1;
}
function init_basic(conn) {
var testDB = conn.getDB('test');
var coll = testDB.updown;
var testName = this.name;
var indexName = 'namedIndex';
assert.commandWorked(testDB.createCollection("capped", {capped: true, size: 10000}),
testName + ' basic createCollection');
var capped = testDB.capped;
// Insert documents
for (var i = 0; i < this.data.numColl; i++) {
if (i < this.data.numCapped) {
assert.writeOK(capped.insert({x: i}), testName + ' basic capped insert');
}
assert.writeOK(coll.insert({x: i, y: i, a: i}), testName + ' basic insert');
}
// Create a named index
assert.commandWorked(coll.createIndex({x: 1}, {name: indexName}),
testName + ' basic createIndex');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_basic(conn) {
var testDB = conn.getDB('test');
var coll = testDB.updown;
var capped = testDB.capped;
var indexes = coll.getIndexes();
var testName = this.name;
var indexNames = this.data.indexNames;
assert.eq(this.data.numColl, coll.count(), testName + ' basic count');
assert.eq(this.data.numIndex, indexes.length, testName + ' basic number of indexes');
// Find the indexes
indexes.forEach(function(index) {
assert(index.name in indexNames, testName + ' basic find index');
});
assert.eq(this.data.numCapped, capped.count(), testName + ' basic capped count');
assert(capped.isCapped(), testName + ' basic isCapped');
}
function init_ttl(conn) {
var testDB = conn.getDB('test');
var coll = testDB.ttl;
var testName = this.name;
var indexName = 'ttl';
this.data.now = (new Date()).getTime();
// Insert documents
for (var i = 0; i < this.data.numColl; i++) {
// past is i hours ago
var past = new Date(this.data.now - (60000 * 60 * i));
assert.writeOK(coll.insert({x: i, date: past}), testName + ' ttl insert');
}
// Ensure TTL monitor will not run
assert.commandWorked(testDB.adminCommand({setParameter: 1, ttlMonitorEnabled: false}),
testName + ' setParameter ttlMonitorEnabled');
// Create a named ttl index, expire after 1 hour
assert.commandWorked(coll.createIndex(
{date: 1},
{name: indexName, expireAfterSeconds: 60 * 60}),
testName + ' ttl createIndex');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_ttl(conn) {
var testDB = conn.getDB('test');
var coll = testDB.ttl;
var testName = this.name;
// Wait until TTL monitor runs at least once
var ttlPass = coll.getDB().serverStatus().metrics.ttl.passes;
assert.soon(function() {
return coll.getDB().serverStatus().metrics.ttl.passes >= ttlPass + 1;
},
testName + " TTL monitor didn't run before timing out.");
// All docs should be expired, except 1
assert.eq(coll.count(), 1, testName + ' ttl count');
}
function init_ttl_partial(conn) {
var testDB = conn.getDB('test');
var coll = testDB.ttl;
var testName = this.name;
var indexName = 'ttl_partial';
this.data.now = (new Date()).getTime();
// Insert documents
for (var i = 0; i < this.data.numColl; i++) {
// past is i hours ago
var past = new Date(this.data.now - (60000 * 60 * i));
assert.writeOK(coll.insert({x: i, date: past}), testName + ' ttl_partial insert1');
assert.writeOK(coll.insert({x: i, date: past, z: i}),
testName + ' ttl_partial insert2');
}
// Ensure TTL monitor will not run
assert.commandWorked(testDB.adminCommand({setParameter: 1, ttlMonitorEnabled: false}),
testName + ' ttl_partial setParameter ttlMonitorEnabled');
// Create a named ttl index, expire after 1 hour
assert.commandWorked(coll.createIndex(
{date: 1},
{name: indexName, expireAfterSeconds: 60 * 60,
partialFilterExpression: {z: {$exists: true}}}),
testName + ' ttl_partial createIndex');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_ttl_partial(conn) {
var testDB = conn.getDB('test');
var coll = testDB.ttl;
var testName = this.name;
// Wait until TTL monitor runs at least once
var ttlPass = coll.getDB().serverStatus().metrics.ttl.passes;
assert.soon(function() {
return coll.getDB().serverStatus().metrics.ttl.passes >= ttlPass + 1;
},
testName + " TTL monitor didn't run before timing out.");
// All documents except for 2 have expired because their 'date' field is more than an hour
// old. However, the partial index does not contain index entries for documents where the
// 'z' field does not exist. Since the TTL monitor deletes documents from the collection by
// doing an index scan, this leaves 50 documents that weren't indexed by the partial index
// (49 of which are technically expired), and 1 that was indexed but hasn't expired yet.
assert.eq(coll.count(), this.data.numColl + 1, testName + ' ttl_partial count');
}
function init_partial_index(conn) {
var testDB = conn.getDB('test');
var coll = testDB.partial;
var testName = this.name;
var indexName = 'partial_index';
// Create a partial index
assert.commandWorked(coll.createIndex(
{x: 1},
{name: indexName,
partialFilterExpression: {a: {$lt: this.data.partial}}}),
testName + ' partial_index createIndex');
// Insert doc that will be indexed
assert.writeOK(coll.insert({x: 1, a: this.data.partial-1}),
testName + ' partial_index insert1');
// In 3.2 insert doc that will not be indexed
assert.writeOK(coll.insert({x: 2, a: this.data.partial}),
testName + ' partial_index insert2');
assert.eq(coll.validate().keysPerIndex["test.partial.$" + indexName],
1,
testName + ' partial_index validate');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_partial_index(conn) {
var testDB = conn.getDB('test');
var coll = testDB.partial;
var testName = this.name;
var indexName = 'partial_index';
assert.eq(coll.validate().keysPerIndex["test.partial.$" + indexName],
1,
testName + ' partial_index validate1');
// Insert doc that will be indexed
assert.writeOK(coll.insert({x: 3, a: this.data.partial - 2}),
testName + ' partial_index insert1');
// In 3.2 insert doc that will not be indexed
assert.writeOK(coll.insert({x: 4, a: this.data.partial + 1}),
testName + ' partial_index insert2');
assert.eq(coll.validate().keysPerIndex["test.partial.$" + indexName],
3,
testName + ' partial_index validate2');
// Remove the documents with partial indexes
assert.writeOK(coll.remove({x: 1, a: this.data.partial - 1}),
testName + ' partial_index remove1');
assert.writeOK(coll.remove({x: 2, a: this.data.partial}),
testName + ' partial_index remove2');
assert.writeOK(coll.remove({x: 3, a: this.data.partial - 2}),
testName + ' partial_index remove3');
assert.writeOK(coll.remove({x: 4, a: this.data.partial + 1}),
testName + ' partial_index remove4');
assert.eq(coll.validate().keysPerIndex["test.partial.$" + indexName], 0,
testName + ' partial_index validate3');
}
function init_document_validation(conn) {
var testDB = conn.getDB('test');
var coll = testDB.docVal;
var testName = this.name;
// Create collection with document validator and insert 1 valid doc
assert.commandWorked(
testDB.createCollection("docVal", {validator: {a: {$exists: true}}}),
testName + ' document_validation createCollection');
assert.writeOK(coll.insert({a: 1}), testName + ' document_validation insert1');
assert.writeError(coll.insert({b: 1}), testName + ' document_validation insert2');
this.data.docVal = coll.count();
}
function verify_document_validation(conn) {
var testDB = conn.getDB('test');
var coll = testDB.docVal;
var testName = this.name;
// Verify documents are there
assert.eq(coll.count(), this.data.docVal, testName + ' document_validation count1');
// Insert, update and upsert documents
assert.writeOK(coll.insert({a: 1}), testName + ' document_validation insert1');
assert.writeOK(coll.insert({b: 1}), testName + ' document_validation insert2');
assert.writeOK(coll.update({b: 1}, {c: 1}), testName + ' document_validation update');
assert.writeOK(coll.update({b: 3}, {c: 1}, {upsert: true}),
testName + ' document_validation upsert');
// Verify documents are there
assert.eq(coll.count(), this.data.docVal + 3, testName + ' document_validation count2');
}
function init_replication(conn){
var testDB = conn.getDB('test');
var testName = this.name;
var rsconf = {_id: 'oplog',
members: [ {_id: 0, host: 'localhost:' + conn.port}],
protocolVersion: 0};
assert.commandWorked(testDB.adminCommand({replSetInitiate : rsconf}),
testName + ' replSetInitiate');
assert(waitForPrimary(testDB), testName + ' host did not become primary');
// Insert 1 document
assert.writeOK(testDB.repl.insert({a: 1}), testName + ' insert');
}
function verify_replication(conn){
var testDB = conn.getDB('test');
var testName = this.name;
assert(waitForPrimary(testDB), testName + ' host did not become primary');
assert.eq(conn.getDB("local").oplog.rs.findOne({ns: "test.repl"}).op,
"i",
testName + ' replication no oplog entry');
}
function init_fullTextSearch(conn) {
var testDB = conn.getDB('test');
var testName = this.name;
var coll = testDB.fullTextSearch;
var indexName = 'content';
var indexSpec = {};
indexSpec[indexName] = 'text';
assert.commandWorked(coll.createIndex(indexSpec, {default_language: "spanish"}),
testName + ' fullTextSearch createIndex');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_fullTextSearch(conn) {
var testDB = conn.getDB('test');
var testName = this.name;
var coll = testDB.fullTextSearch;
var indexName = 'content';
assert.eq(1,
coll.getIndexes().filter(function(z){ return z.name == indexName + "_text"; }).length,
testName + ' fullTextSearch getIndexes');
}
function init_geo(conn) {
var testDB = conn.getDB('test');
var testName = this.name;
var coll = testDB.geo;
var indexName = 'geo';
var indexSpec = {};
indexSpec[indexName] = '2dsphere';
assert.commandWorked(coll.createIndex(indexSpec),
testName + ' geo createIndex');
this.data.indexNames[indexName] = 1;
this.data.numIndex = coll.getIndexes().length;
}
function verify_geo(conn) {
var testDB = conn.getDB('test');
var testName = this.name;
var coll = testDB.geo;
var indexName = 'geo';
assert.eq(1,
coll.getIndexes().filter(
function(z){ return z.name == indexName + "_2dsphere"; }).length,
testName + ' geo getIndexes');
}
// Upgrade/downgrade tests
var tests = [
// Upgrade with mmapv1
{
name: "Upgrade - mmapv1",
fromBinVersion: "last-stable",
toBinVersion: "latest",
storageEngine: "mmapv1",
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
},
init: [init_basic],
verify: [verify_basic]
},
// Downgrade with mmapv1
{
name: "Downgrade - mmapv1",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "mmapv1",
options: {setParameter: "ttlMonitorSleepSecs=3"},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
partial: 5
},
init: [
init_basic,
init_ttl,
init_partial_index,
init_document_validation
],
verify: [
verify_basic,
verify_ttl,
verify_partial_index,
verify_document_validation
]
},
// Downgrade with mmapv1 - ttl w/partial index
{
name: "Downgrade - mmapv1: ttl with partial index filter",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "mmapv1",
options: {setParameter: "ttlMonitorSleepSecs=3"},
data: {
indexNames: {_id_: 1},
numColl: 50,
partial: 5
},
init: [init_ttl_partial],
verify: [verify_ttl_partial]
},
// Downgrade with mmapv1 - fullTextSearch index
{
name: "Downgrade - mmapv1: fullTextSearch index",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "mmapv1",
data: {
indexNames: {_id_: 1},
failedConn: true
},
init: [init_fullTextSearch],
verify: [verify_fullTextSearch]
},
// Downgrade with mmapv1 - geo index
{
name: "Downgrade - mmapv1: geo index",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "mmapv1",
data: {
indexNames: {_id_: 1},
failedConn: true
},
init: [init_geo],
verify: [verify_geo]
},
// Downgrade with mmapv1 - oplog
{
name: "Downgrade - mmapv1: oplog",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "mmapv1",
options: {replSet: "oplog"},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
},
init: [
init_replication,
init_basic
],
verify: [
verify_replication,
verify_basic
],
},
// Upgrade with wiredTiger
{
name: "Upgrade - wiredTiger",
fromBinVersion: "last-stable",
toBinVersion: "latest",
storageEngine: "wiredTiger",
options: {setParameter: "ttlMonitorSleepSecs=3"},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
},
init: [
init_basic,
init_ttl,
init_fullTextSearch,
init_geo
],
verify: [
verify_basic,
verify_ttl,
verify_fullTextSearch,
verify_geo
]
},
// Upgrade with wiredTiger LSM, nojournal
{
name: "Upgrade - wiredTiger: LSM, nojournal",
fromBinVersion: "last-stable",
toBinVersion: "latest",
storageEngine: "wiredTiger",
options: {
wiredTigerCollectionConfigString: "type=lsm",
wiredTigerIndexConfigString: "type=lsm",
nojournal: "",
},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
},
init: [init_basic],
verify: [verify_basic]
},
// Downgrade with wiredTiger
{
name: "Downgrade - wiredTiger",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "wiredTiger",
options: {replSet: "oplog"},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
partial: 5,
},
init: [
init_replication,
init_basic,
init_document_validation
],
verify: [
verify_replication,
verify_basic,
verify_document_validation
]
},
// Downgrade with wiredTiger - ttl w/partial index
{
name: "Downgrade - wiredTiger: ttl with partial index filter",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "wiredTiger",
options: {setParameter: "ttlMonitorSleepSecs=3"},
data: {
indexNames: {_id_: 1},
numCapped: 10,
numColl: 50,
partial: 5,
},
init: [init_ttl_partial],
verify: [verify_ttl_partial]
},
// Downgrade with wiredTiger - fullTextSearch index
{
name: "Downgrade - wiredTiger: fullTextSearch index",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "wiredTiger",
data: {
indexNames: {_id_: 1},
failedConn: true
},
init: [init_fullTextSearch],
verify: [verify_fullTextSearch]
},
// Downgrade with wiredTiger - geo index
{
name: "Downgrade - wiredTiger: geo index",
fromBinVersion: "latest",
toBinVersion: "last-stable",
storageEngine: "wiredTiger",
data: {
indexNames: {_id_: 1},
failedConn: true
},
init: [init_geo],
verify: [verify_geo]
},
];
var conn;
var expectedConn;
tests.forEach(function(test) {
jsTestLog(test.name);
// Set mongod options
var mongodOptions = {
remember: true,
cleanData: true,
binVersion: test.fromBinVersion,
storageEngine: test.storageEngine,
smallfiles: ""
};
// Additional mongod options
if (test.options) {
for (var option in test.options) {
mongodOptions[option] = test.options[option];
}
}
// Start mongod
var conn = MongoRunner.runMongod(mongodOptions);
assert(conn, test.name + ' start');
// Change writeMode to commands
conn.forceWriteMode("commands");
// Execute all init functions
test.init.forEach(function(func) {
func.call(test, conn);
});
// Stop existing mongod
MongoRunner.stopMongod(conn);
// Restart mongod under a different version
mongodOptions.restart = conn;
mongodOptions.binVersion = test.toBinVersion;
mongodOptions.cleanData = false;
conn = MongoRunner.runMongod(mongodOptions);
if (test.data.failedConn) {
// We are expecting a mongod failure
expectedConn = null;
} else {
expectedConn = conn || true;
}
assert.eq(conn, expectedConn, test.name + ' restart');
if (conn) {
// Change writeMode to commands
conn.forceWriteMode("commands");
// Execute all verify functions
test.verify.forEach(function(func) {
func.call(test, conn);
});
// Test finished, stop mongod
MongoRunner.stopMongod(conn);
}
});
}());