Skip to content

Commit af60369

Browse files
authored
feat: add sharded migration helpers MONGOSH-2139 (#2542)
1 parent 2d0cf48 commit af60369

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

packages/i18n/src/locales/en_US.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,6 +2124,18 @@ const translations: Catalog = {
21242124
'Disable balancing on a single collection in a sharded database. Does not affect balancing of other collections in a sharded cluster.',
21252125
example: 'sh.disableBalancing(ns)',
21262126
},
2127+
enableMigrations: {
2128+
link: 'https://mongodb.com/docs/manual/reference/method/sh.enableMigrations',
2129+
description:
2130+
'Enables migrations for a specific collection. Uses `setAllowMigrations` admin command.',
2131+
example: 'sh.enableMigrations(ns)',
2132+
},
2133+
disableMigrations: {
2134+
link: 'https://mongodb.com/docs/manual/reference/method/sh.disableMigrations',
2135+
description:
2136+
'Disables migrations for a specific collection. Uses `setAllowMigrations` admin command.',
2137+
example: 'sh.disableMigrations(ns)',
2138+
},
21272139
getBalancerState: {
21282140
link: 'https://mongodb.com/docs/manual/reference/method/sh.getBalancerState',
21292141
description:

packages/shell-api/src/helpers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,9 @@ export async function getPrintableShardStatus(
597597
];
598598
}
599599

600+
collRes.allowMigrations =
601+
coll.permitMigrations !== false && coll.allowMigrations !== false;
602+
600603
const chunksRes = [];
601604
const chunksCollMatch = buildConfigChunksCollectionMatch(coll);
602605
const chunks = await (

packages/shell-api/src/shard.spec.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,76 @@ describe('Shard', function () {
12321232
expect(warnSpy.calledOnce).to.equal(true);
12331233
});
12341234
});
1235+
describe('disableMigrations', function () {
1236+
this.beforeEach(() => {
1237+
serviceProvider.runCommandWithCheck.onFirstCall().resolves({
1238+
ok: 1,
1239+
msg: 'not dbgrid',
1240+
});
1241+
serviceProvider.runCommandWithCheck.onSecondCall().resolves({ ok: 1 });
1242+
});
1243+
1244+
it('warns if not mongos', async function () {
1245+
await shard.disableMigrations('ns');
1246+
expect(warnSpy.calledOnce).to.equal(true);
1247+
});
1248+
1249+
it('calls serviceProvider.runCommandWithCheck', async function () {
1250+
await shard.disableMigrations('ns');
1251+
1252+
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
1253+
ADMIN_DB,
1254+
{
1255+
setAllowMigrations: 'ns',
1256+
allowMigrations: false,
1257+
}
1258+
);
1259+
});
1260+
1261+
it('throws if serviceProvider.runCommandWithCheck rejects', async function () {
1262+
const expectedError = new Error();
1263+
serviceProvider.runCommandWithCheck
1264+
.onSecondCall()
1265+
.rejects(expectedError);
1266+
const caughtError = await shard.disableMigrations('ns').catch((e) => e);
1267+
expect(caughtError).to.equal(expectedError);
1268+
});
1269+
});
1270+
describe('enableMigrations', function () {
1271+
this.beforeEach(() => {
1272+
serviceProvider.runCommandWithCheck.onFirstCall().resolves({
1273+
ok: 1,
1274+
msg: 'not dbgrid',
1275+
});
1276+
serviceProvider.runCommandWithCheck.onSecondCall().resolves({ ok: 1 });
1277+
});
1278+
1279+
it('warns if not mongos', async function () {
1280+
await shard.enableMigrations('ns');
1281+
expect(warnSpy.calledOnce).to.equal(true);
1282+
});
1283+
1284+
it('calls serviceProvider.runCommandWithCheck', async function () {
1285+
await shard.enableMigrations('ns');
1286+
1287+
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
1288+
ADMIN_DB,
1289+
{
1290+
setAllowMigrations: 'ns',
1291+
allowMigrations: true,
1292+
}
1293+
);
1294+
});
1295+
1296+
it('throws if serviceProvider.runCommandWithCheck rejects', async function () {
1297+
const expectedError = new Error();
1298+
serviceProvider.runCommandWithCheck
1299+
.onSecondCall()
1300+
.rejects(expectedError);
1301+
const caughtError = await shard.enableMigrations('ns').catch((e) => e);
1302+
expect(caughtError).to.equal(expectedError);
1303+
});
1304+
});
12351305
describe('getBalancerState', function () {
12361306
it('returns whatever serviceProvider.find returns', async function () {
12371307
serviceProvider.runCommandWithCheck.resolves({
@@ -2530,6 +2600,71 @@ describe('Shard', function () {
25302600
);
25312601
});
25322602
});
2603+
describe('collection migrations', function () {
2604+
let db: Database;
2605+
2606+
const dbName = 'shard-status-test';
2607+
const ns = `${dbName}.test`;
2608+
2609+
beforeEach(async function () {
2610+
db = sh._database.getSiblingDB(dbName);
2611+
await db.getCollection('test').insertOne({ key: 1 });
2612+
await db.getCollection('test').createIndex({ key: 1 });
2613+
await sh.enableSharding(dbName);
2614+
await sh.shardCollection(ns, { key: 1 });
2615+
});
2616+
2617+
afterEach(async function () {
2618+
await db.dropDatabase();
2619+
});
2620+
2621+
const checkMigrationsEnabled = async (): Promise<boolean> => {
2622+
return (await sh.status()).value.databases.find(
2623+
(d) => d.database._id === dbName
2624+
)?.collections[ns].allowMigrations;
2625+
};
2626+
2627+
it('has migrations enabled by default', async function () {
2628+
expect(await checkMigrationsEnabled()).to.be.true;
2629+
});
2630+
2631+
it('can disable migrations', async function () {
2632+
expect((await sh.disableMigrations(ns)).ok).to.equal(1);
2633+
expect(await checkMigrationsEnabled()).to.be.false;
2634+
});
2635+
2636+
it('can enable migrations', async function () {
2637+
// Enabled by default, so disable first
2638+
expect((await sh.disableMigrations(ns)).ok).to.equal(1);
2639+
expect(await checkMigrationsEnabled()).to.be.false;
2640+
2641+
expect((await sh.enableMigrations(ns)).ok).to.equal(1);
2642+
expect(await checkMigrationsEnabled()).to.be.true;
2643+
});
2644+
2645+
it('disabling migrations is idempotent', async function () {
2646+
expect(await checkMigrationsEnabled()).to.be.true;
2647+
2648+
expect((await sh.disableMigrations(ns)).ok).to.equal(1);
2649+
expect(await checkMigrationsEnabled()).to.be.false;
2650+
2651+
// Run disable again to check idempotency
2652+
expect((await sh.disableMigrations(ns)).ok).to.equal(1);
2653+
expect(await checkMigrationsEnabled()).to.be.false;
2654+
});
2655+
2656+
it('enabling migrations is idempotent', async function () {
2657+
expect(await checkMigrationsEnabled()).to.be.true;
2658+
2659+
// Enabling when already enabled should not do anything
2660+
expect((await sh.enableMigrations(ns)).ok).to.equal(1);
2661+
expect(await checkMigrationsEnabled()).to.be.true;
2662+
2663+
// Run enable again to check idempotency
2664+
expect((await sh.enableMigrations(ns)).ok).to.equal(1);
2665+
expect(await checkMigrationsEnabled()).to.be.true;
2666+
});
2667+
});
25332668
describe('automerge', function () {
25342669
it('not shown if sh.status() if not explicitly enabled', async function () {
25352670
// It might be explicitly set from 7.0

packages/shell-api/src/shard.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,39 @@ export default class Shard<
556556
)) as UpdateResult;
557557
}
558558

559+
private async _setAllowMigrations(
560+
ns: string,
561+
allowMigrations: boolean
562+
): Promise<Document> {
563+
const apiCall = `${allowMigrations ? 'enable' : 'disable'}Migrations`;
564+
assertArgsDefinedType([ns], ['string'], `Shard.${apiCall}`);
565+
this._emitShardApiCall(apiCall, { ns });
566+
567+
const helloResult = await this._database._maybeCachedHello();
568+
if (helloResult.msg !== 'isdbgrid') {
569+
await this._database._instanceState.printWarning(
570+
'MongoshWarning: [SHAPI-10003] You are not connected to a mongos. This command may not work as expected.'
571+
);
572+
}
573+
574+
return await this._database._runAdminCommand({
575+
setAllowMigrations: ns,
576+
allowMigrations,
577+
});
578+
}
579+
580+
@returnsPromise
581+
@apiVersions([])
582+
async enableMigrations(ns: string): Promise<Document> {
583+
return await this._setAllowMigrations(ns, true);
584+
}
585+
586+
@returnsPromise
587+
@apiVersions([])
588+
async disableMigrations(ns: string): Promise<Document> {
589+
return await this._setAllowMigrations(ns, false);
590+
}
591+
559592
@returnsPromise
560593
@apiVersions([])
561594
async getBalancerState(): Promise<boolean> {

0 commit comments

Comments
 (0)