mongo/jstests/noPassthrough/api_version/api_version_parameters_shel...

307 lines
13 KiB
JavaScript

/**
* Test the shell's --apiVersion and other options related to the MongoDB Stable API, and
* test passing API parameters to the Mongo() constructor.
*
* @tags: [
* requires_persistence,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
const testCases = [
// [requireApiVersion server parameter, expect success, command, API parameters]
[false, true, {ping: 1}, {}],
[false, true, {ping: 1}, {version: '1'}],
[false, true, {count: 'collection'}, {version: '1'}],
[false, true, {getLog: 'global'}, {version: '1'}],
[false, true, {getLog: 'global'}, {version: '1', deprecationErrors: true}],
// getLog isn't in API Version 1, so it's banned with strict: true.
[false, false, {getLog: 'global'}, {version: '1', strict: true}],
[false, true, {ping: 1}, {version: '1', strict: true}],
[false, true, {testDeprecation: 1}, {version: '1', strict: true}],
[false, false, {testDeprecation: 1}, {version: '1', deprecationErrors: true}],
[false, false, {testDeprecation: 1}, {version: '1', strict: true, deprecationErrors: true}],
// tests with setParameter requireApiVersion: true.
[true, true, {count: 'collection'}, {version: '1'}],
[true, true, {count: 'collection'}, {version: '1', strict: true}],
[true, false, {ping: 1}, {}],
[true, true, {ping: 1}, {version: '1'}],
];
function runShellWithScript(port, requireApiVersion, expectSuccess, script, api) {
let shellArgs = [];
if (api.version) {
shellArgs.push('--apiVersion', api.version);
}
if (api.strict) {
shellArgs.push('--apiStrict');
}
if (api.deprecationErrors) {
shellArgs.push('--apiDeprecationErrors');
}
jsTestLog(`Run shell with script ${script} and args ${tojson(shellArgs)},` +
` requireApiVersion = ${requireApiVersion}, expectSuccess = ${expectSuccess}`);
const result = runMongoProgram.apply(
null, ['mongo', '--port', port, '--eval', script].concat(shellArgs || []));
if (expectSuccess) {
assert.eq(
result,
0,
`Error running shell with script ${tojson(script)} and args ${tojson(shellArgs)}`);
} else {
assert.neq(result,
0,
`Unexpected success running shell with` +
` script ${tojson(script)} and args ${tojson(shellArgs)}`);
}
}
function runShellWithCommand(port, requireApiVersion, expectSuccess, command, api) {
// Test runCommand and runReadCommand.
const scripts = [
`assert.commandWorked(db.getSiblingDB("admin").runCommand(${tojson(command)}))`,
`assert.commandWorked(db.getSiblingDB("admin").runReadCommand(${tojson(command)}))`,
];
for (const script of scripts) {
runShellWithScript(port, requireApiVersion, expectSuccess, script, api);
}
}
function newMongo(port, requireApiVersion, expectSuccess, command, api) {
jsTestLog(`Construct Mongo object with command ${tojson(command)} and args ${tojson(api)},` +
` requireApiVersion = ${requireApiVersion}, expectSuccess = ${expectSuccess}`);
if (expectSuccess) {
const m = new Mongo(
`mongodb://localhost:${port}`, undefined /* encryptedDBClientCallback */, {api: api});
const reply = m.adminCommand(command);
assert.commandWorked(reply, command);
} else {
let m;
try {
m = new Mongo(`mongodb://localhost:${port}`,
undefined /* encryptedDBClientCallback */,
{api: api});
} catch (e) {
// The constructor threw, but we expected failure.
print(e);
return;
}
const reply = m.adminCommand(command);
assert.commandFailed(reply, command);
}
}
const mongod = MongoRunner.runMongod({verbose: 2});
for (let [requireApiVersion, successExpected, command, api] of testCases) {
const m = new Mongo(`localhost:${mongod.port}`, undefined, {api: {version: '1'}});
assert.commandWorked(
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
runShellWithCommand(mongod.port, requireApiVersion, successExpected, command, api);
newMongo(mongod.port, requireApiVersion, successExpected, command, api);
}
let m = new Mongo(`localhost:${mongod.port}`, undefined, {api: {version: '1'}});
assert.commandWorked(m.getDB('admin').runCommand(
{insert: 'collection', documents: [{}, {}, {}, {}, {}, {}], apiVersion: '1'}));
for (let requireApiVersion of [false, true]) {
// Omit api = {}, that's tested elsewhere.
for (let api of [{version: '1'},
{version: '1', strict: true},
{version: '1', deprecationErrors: true},
{version: '1', strict: true, deprecationErrors: true},
]) {
assert.commandWorked(
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
/*
* Test getMore. Create a cursor with the right API version parameters. Use an explicit lsid
* to override implicit session creation.
*/
const lsid = UUID();
const versionedConn = new Mongo(`localhost:${mongod.port}`, undefined, {api: api});
const findReply = assert.commandWorked(versionedConn.getDB('admin').runCommand(
{find: 'collection', batchSize: 1, lsid: {id: lsid}}));
const getMoreCmd = {
getMore: findReply.cursor.id,
collection: 'collection',
lsid: {id: lsid},
batchSize: 1
};
runShellWithCommand(
mongod.port, requireApiVersion, true /* expectSuccess */, getMoreCmd, api);
newMongo(mongod.port, requireApiVersion, true /* expectSuccess */, getMoreCmd, api);
/*
* Test unacknowledged writes (OP_MSG with moreToCome).
*/
versionedConn.getDB('admin')['collection2'].insertMany([{}, {}]);
const deleteScript = "db.getSiblingDB('admin').collection2.deleteOne({}, {w: 0});";
runShellWithScript(
mongod.port, requireApiVersion, true /* expectSuccess */, deleteScript, api);
// The delete succeeds, collection2 had 2 records, it soon has 1. Use assert.soon since the
// write is unacknowledged. Use countDocuments which is apiStrict-compatible.
assert.soon(() => {
return 1 === versionedConn.getDB('admin')['collection2'].countDocuments({});
});
const deleteCmd = {
delete: 'collection2',
deletes: [{q: {}, limit: 1}],
writeConcern: {w: 0}
};
newMongo(mongod.port, requireApiVersion, true /* expectSuccess */, deleteCmd, api);
// The delete succeeds, collection2 soon has 0 records.
assert.soon(() => {
return 0 === versionedConn.getDB('admin')['collection2'].countDocuments({});
});
}
}
/*
* Shell-specific tests.
*/
// Version 2 is not supported.
runShellWithCommand(mongod.port, false, false, {ping: 1}, {version: '2'});
// apiVersion is required if strict or deprecationErrors is included
runShellWithCommand(mongod.port, false, false, {ping: 1}, {strict: true});
runShellWithCommand(mongod.port, false, false, {ping: 1}, {deprecationErrors: true});
runShellWithCommand(mongod.port, false, false, {ping: 1}, {strict: true, deprecationErrors: true});
if (m.adminCommand('buildinfo').modules.indexOf('enterprise') > -1) {
/*
* Test that we can call buildinfo while assembling the shell prompt, in order to determine that
* this is MongoDB Enterprise, although buildinfo is not in API Version 1 and the shell is
* running with --apiStrict.
*/
const testPrompt = "assert(RegExp('MongoDB Enterprise').test(defaultPrompt()))";
const result = runMongoProgram(
'mongo', '--port', mongod.port, '--apiVersion', '1', '--apiStrict', '--eval', testPrompt);
assert.eq(result, 0, `Error running shell with script '${testPrompt}'`);
}
// Test that shell helpers which return a cursor do not accept API versioning parameters. This is to
// avoid the potential pitfall of sending API parameters on the initial command but not on
// subsequent getMore's.
const coll = m.getDB("test").collection;
assert.throws(() => coll.aggregate([], {apiVersion: 1}));
assert.throws(() => coll.aggregate([], {apiStrict: true}));
assert.throws(() => coll.aggregate([], {apiDeprecationErrors: true}));
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiVersion: 1}));
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiStrict: true}));
assert.throws(() => coll.find({}, {}, 1, 0, 1, {apiDeprecationErrors: true}));
assert.throws(() => coll.watch({}, {apiVersion: 1}));
assert.throws(() => coll.watch({}, {apiStrict: true}));
assert.throws(() => coll.watch({}, {apiDeprecationErrors: true}));
assert.throws(() => coll.getDB().watch({}, {apiVersion: 1}));
assert.throws(() => coll.getDB().watch({}, {apiStrict: true}));
assert.throws(() => coll.getDB().watch({}, {apiDeprecationErrors: true}));
/*
* Mongo-specific tests.
*/
assert.throws(() => {
new Mongo(mongod.port, null, "not an object");
}, [], "Mongo() constructor should check that options argument is an object");
assert.throws(() => {
new Mongo(mongod.port, null, {api: "not an object"});
}, [], "Mongo() constructor should check that 'api' is an object");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: 1}});
}, [], "Mongo() constructor should reject API version 1 (as a number)");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: '2'}});
}, [], "Mongo() constructor should reject API version 2");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: '1', strict: 1}});
}, [], "Mongo() constructor should reject strict: 1");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: '1', strict: 'asdf'}});
}, [], "Mongo() constructor should reject strict: 'asdf");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: '1', deprecationErrors: 1}});
}, [], "Mongo() constructor should reject deprecationErrors: 1");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {version: '1', deprecationErrors: 'asdf'}});
}, [], "Mongo() constructor should reject deprecationErrors: 'asdf'");
// apiVersion is required if strict or deprecationErrors is included
assert.throws(() => {
new Mongo(mongod.port, null, {api: {strict: true}});
}, [], "Mongo() constructor should reject 'strict' without 'version'");
assert.throws(() => {
new Mongo(mongod.port, null, {api: {deprecationErrors: true}});
}, [], "Mongo() constructor should reject 'deprecationErrors' without 'version'");
MongoRunner.stopMongod(mongod);
const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();
const primaryPort = rst.getPrimary().port;
/*
* Test that we can call replSetGetStatus while assembling the shell prompt, although
* replSetGetStatus is not in API Version 1 and the shell is running with --apiStrict.
*/
const testPrompt = "assert(RegExp('PRIMARY').test(defaultPrompt()))";
const result = runMongoProgram(
'mongo', '--port', primaryPort, '--apiVersion', '1', '--apiStrict', '--eval', testPrompt);
assert.eq(result, 0, `Error running shell with script '${testPrompt}'`);
/*
* Test transaction-continuing commands with API parameters.
*/
m = new Mongo(`localhost:${primaryPort}`, undefined, {api: {version: '1'}});
for (let requireApiVersion of [false, true]) {
// Omit api = {}, that's tested elsewhere.
for (let api of [{version: '1'},
{version: '1', strict: true},
{version: '1', deprecationErrors: true},
{version: '1', strict: true, deprecationErrors: true},
]) {
assert.commandWorked(
m.getDB("admin").runCommand({setParameter: 1, requireApiVersion: requireApiVersion}));
// Start a transaction with the right API version parameters. Use an explicit lsid to
// override implicit session creation.
const lsid = UUID();
const versionedConn = new Mongo(`localhost:${primaryPort}`, undefined, {api: api});
assert.commandWorked(versionedConn.getDB('admin').runCommand({
find: 'collection',
lsid: {id: lsid},
txnNumber: NumberLong(1),
autocommit: false,
startTransaction: true
}));
const continueCmd =
{find: 'collection', lsid: {id: lsid}, txnNumber: NumberLong(1), autocommit: false};
runShellWithCommand(
primaryPort, requireApiVersion, true /* expectSuccess */, continueCmd, api);
newMongo(primaryPort, requireApiVersion, true /* expectSuccess */, continueCmd, api);
}
}
assert.commandWorked(m.getDB('admin').runCommand({setParameter: 1, requireApiVersion: false}));
rst.stopSet();