Skip to content

Commit d0132aa

Browse files
authored
feat(shell-api): add sh.isConfigShardEnabled and listShards helpers MONGOSH-1919 (#2400)
* feat: add sh.isConfigShardEnabled helper * feat: add listShards helper
1 parent eb57ce8 commit d0132aa

File tree

4 files changed

+257
-17
lines changed

4 files changed

+257
-17
lines changed

packages/i18n/src/locales/en_US.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,18 @@ const translations: Catalog = {
21482148
'Abort the current unshardCollection operation on a given collection',
21492149
example: 'sh.abortUnshardCollection(ns)',
21502150
},
2151+
listShards: {
2152+
link: 'https://docs.mongodb.com/manual/reference/method/sh.listShards',
2153+
description:
2154+
'Returns a list of the configured shards in a sharded cluster',
2155+
example: 'sh.listShards()',
2156+
},
2157+
isConfigShardEnabled: {
2158+
link: 'https://docs.mongodb.com/manual/reference/method/sh.isConfigShardEnabled',
2159+
description:
2160+
'Returns a document with an `enabled: <boolean>` field indicating whether the cluster is configured as embedded config server cluster. If it is, then the config shard host and tags are also returned.',
2161+
example: 'sh.isConfigShardEnabled()',
2162+
},
21512163
},
21522164
},
21532165
},

packages/shell-api/src/helpers.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -683,21 +683,23 @@ export async function getPrintableShardStatus(
683683
return result;
684684
}
685685

686+
export type ShardInfo = {
687+
_id: string;
688+
host: string;
689+
state: number;
690+
tags?: string[];
691+
topologyTime: Timestamp;
692+
replSetConfigVersion: Long;
693+
};
694+
686695
export type ShardingStatusResult = {
687696
shardingVersion: {
688697
_id: number;
689698
clusterId: ObjectId;
690699
/** This gets deleted when it is returned from getPrintableShardStatus */
691700
currentVersion?: number;
692701
};
693-
shards: {
694-
_id: string;
695-
host: string;
696-
state: number;
697-
tags: string[];
698-
topologyTime: Timestamp;
699-
replSetConfigVersion: Long;
700-
}[];
702+
shards: ShardInfo[];
701703
[mongoses: `${string} mongoses`]:
702704
| 'none'
703705
| {

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

Lines changed: 204 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,185 @@ describe('Shard', function () {
21082108
expect(caughtError).to.equal(expectedError);
21092109
});
21102110
});
2111+
2112+
describe('listShards', function () {
2113+
this.beforeEach(function () {
2114+
serviceProvider.runCommandWithCheck.resolves({
2115+
ok: 1,
2116+
msg: 'isdbgrid',
2117+
});
2118+
});
2119+
2120+
it('calls serviceProvider.runCommandWithCheck', async function () {
2121+
await shard.listShards();
2122+
2123+
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
2124+
ADMIN_DB,
2125+
{
2126+
listShards: 1,
2127+
}
2128+
);
2129+
});
2130+
2131+
it('returns the shards returned by runCommandWithCheck', async function () {
2132+
serviceProvider.runCommandWithCheck.resolves({
2133+
ok: 1,
2134+
msg: 'isdbgrid',
2135+
shards: [
2136+
{
2137+
_id: 'shard1',
2138+
host: 'shard1/foo.bar:27017',
2139+
},
2140+
{
2141+
_id: 'shard2',
2142+
host: 'shard2/foo.bar:27018',
2143+
},
2144+
],
2145+
});
2146+
const result = await shard.listShards();
2147+
expect(result).to.deep.equal([
2148+
{
2149+
_id: 'shard1',
2150+
host: 'shard1/foo.bar:27017',
2151+
},
2152+
{
2153+
_id: 'shard2',
2154+
host: 'shard2/foo.bar:27018',
2155+
},
2156+
]);
2157+
});
2158+
2159+
it('returns empty array when shards field is not present', async function () {
2160+
const result = await shard.listShards();
2161+
expect(result).to.deep.equal([]);
2162+
});
2163+
2164+
it('throws if serviceProvider.runCommandWithCheck rejects', async function () {
2165+
const expectedError = new Error('unreachable');
2166+
serviceProvider.runCommandWithCheck.rejects(expectedError);
2167+
const caughtError = await shard.listShards().catch((e) => e);
2168+
expect(caughtError).to.equal(expectedError);
2169+
});
2170+
2171+
it('throws if not mongos', async function () {
2172+
serviceProvider.runCommandWithCheck.resolves({
2173+
ok: 1,
2174+
msg: 'not dbgrid',
2175+
});
2176+
await shard.listShards();
2177+
expect(warnSpy.calledOnce).to.be.true;
2178+
});
2179+
});
2180+
2181+
describe('isConfigShardEnabled', function () {
2182+
this.beforeEach(function () {
2183+
serviceProvider.runCommandWithCheck.resolves({
2184+
ok: 1,
2185+
msg: 'isdbgrid',
2186+
});
2187+
});
2188+
2189+
it('calls serviceProvider.runCommandWithCheck', async function () {
2190+
await shard.isConfigShardEnabled();
2191+
2192+
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
2193+
ADMIN_DB,
2194+
{
2195+
listShards: 1,
2196+
}
2197+
);
2198+
});
2199+
2200+
it("returns false when listShards doesn't contain config shard", async function () {
2201+
serviceProvider.runCommandWithCheck.resolves({
2202+
ok: 1,
2203+
msg: 'isdbgrid',
2204+
shards: [
2205+
{
2206+
_id: 'shard1',
2207+
host: 'shard1/foo.bar:27017',
2208+
},
2209+
{
2210+
_id: 'shard2',
2211+
host: 'shard2/foo.bar:27018',
2212+
},
2213+
],
2214+
});
2215+
const result = await shard.isConfigShardEnabled();
2216+
expect(result).to.deep.equal({ enabled: false });
2217+
});
2218+
2219+
it('returns true and shard info when listShards contains config shard', async function () {
2220+
serviceProvider.runCommandWithCheck.resolves({
2221+
ok: 1,
2222+
msg: 'isdbgrid',
2223+
shards: [
2224+
{
2225+
_id: 'shard1',
2226+
host: 'shard1/foo.bar:27017',
2227+
},
2228+
{
2229+
_id: 'shard2',
2230+
host: 'shard2/foo.bar:27018',
2231+
},
2232+
{
2233+
_id: 'config',
2234+
host: 'shard3/foo.bar:27019',
2235+
},
2236+
],
2237+
});
2238+
const result = await shard.isConfigShardEnabled();
2239+
expect(result).to.deep.equal({
2240+
enabled: true,
2241+
host: 'shard3/foo.bar:27019',
2242+
});
2243+
});
2244+
2245+
it('returns config shard tags', async function () {
2246+
serviceProvider.runCommandWithCheck.resolves({
2247+
ok: 1,
2248+
msg: 'isdbgrid',
2249+
shards: [
2250+
{
2251+
_id: 'shard1',
2252+
host: 'shard1/foo.bar:27017',
2253+
},
2254+
{
2255+
_id: 'shard2',
2256+
host: 'shard2/foo.bar:27018',
2257+
},
2258+
{
2259+
_id: 'config',
2260+
host: 'shard3/foo.bar:27019',
2261+
tags: ['tag1', 'tag2'],
2262+
},
2263+
],
2264+
});
2265+
2266+
const result = await shard.isConfigShardEnabled();
2267+
expect(result).to.deep.equal({
2268+
enabled: true,
2269+
host: 'shard3/foo.bar:27019',
2270+
tags: ['tag1', 'tag2'],
2271+
});
2272+
});
2273+
2274+
it('throws if serviceProvider.runCommandWithCheck rejects', async function () {
2275+
const expectedError = new Error('unreachable');
2276+
serviceProvider.runCommandWithCheck.rejects(expectedError);
2277+
const caughtError = await shard.isConfigShardEnabled().catch((e) => e);
2278+
expect(caughtError).to.equal(expectedError);
2279+
});
2280+
2281+
it('throws if not mongos', async function () {
2282+
serviceProvider.runCommandWithCheck.resolves({
2283+
ok: 1,
2284+
msg: 'not dbgrid',
2285+
});
2286+
await shard.isConfigShardEnabled();
2287+
expect(warnSpy.calledOnce).to.be.true;
2288+
});
2289+
});
21112290
});
21122291

21132292
describe('integration', function () {
@@ -2355,15 +2534,11 @@ describe('Shard', function () {
23552534
describe('tags', function () {
23562535
it('creates a zone', async function () {
23572536
expect((await sh.addShardTag(`${shardId}-1`, 'zone1')).ok).to.equal(1);
2358-
expect((await sh.status()).value.shards[1]?.tags).to.deep.equal([
2359-
'zone1',
2360-
]);
2537+
expect((await sh.listShards())[1]?.tags).to.deep.equal(['zone1']);
23612538
expect((await sh.addShardToZone(`${shardId}-0`, 'zone0')).ok).to.equal(
23622539
1
23632540
);
2364-
expect((await sh.status()).value.shards[0]?.tags).to.deep.equal([
2365-
'zone0',
2366-
]);
2541+
expect((await sh.listShards())[0]?.tags).to.deep.equal(['zone0']);
23672542
});
23682543
it('sets a zone key range', async function () {
23692544
expect(
@@ -2408,11 +2583,11 @@ describe('Shard', function () {
24082583
expect(
24092584
(await sh.removeShardFromZone(`${shardId}-1`, 'zone1')).ok
24102585
).to.equal(1);
2411-
expect((await sh.status()).value.shards[1].tags).to.deep.equal([]);
2586+
expect((await sh.listShards())[1].tags).to.deep.equal([]);
24122587
expect((await sh.removeShardTag(`${shardId}-0`, 'zone0')).ok).to.equal(
24132588
1
24142589
);
2415-
expect((await sh.status()).value.shards[0].tags).to.deep.equal([]);
2590+
expect((await sh.listShards())[0].tags).to.deep.equal([]);
24162591
});
24172592
it('shows a full tag list when there are 20 or less tags', async function () {
24182593
const db = instanceState.currentDb.getSiblingDB(dbName);
@@ -3173,6 +3348,27 @@ describe('Shard', function () {
31733348
expect(await cursor.toArray()).to.deep.equal([]);
31743349
});
31753350
});
3351+
3352+
describe('listShards', function () {
3353+
it('returns the list of shards', async function () {
3354+
const result = await sh.listShards();
3355+
expect(result).to.be.an('array');
3356+
expect(result).to.have.lengthOf(2);
3357+
3358+
expect(result[0]._id).to.equal(`${shardId}-0`);
3359+
expect(result[0].host).to.contain(`${shardId}-0`);
3360+
3361+
expect(result[1]._id).to.equal(`${shardId}-1`);
3362+
expect(result[1].host).to.contain(`${shardId}-1`);
3363+
});
3364+
3365+
it('matches the output of status().shards', async function () {
3366+
const listShardsResult = await sh.listShards();
3367+
const statusResultShards = (await sh.status()).value.shards;
3368+
3369+
expect(listShardsResult).to.deep.equal(statusResultShards);
3370+
});
3371+
});
31763372
});
31773373

31783374
describe('integration chunks', function () {

packages/shell-api/src/shard.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
Document,
1313
CheckMetadataConsistencyOptions,
1414
} from '@mongosh/service-provider-core';
15-
import type { ShardingStatusResult } from './helpers';
15+
import type { ShardInfo, ShardingStatusResult } from './helpers';
1616
import {
1717
assertArgsDefinedType,
1818
getConfigDB,
@@ -811,4 +811,34 @@ export default class Shard extends ShellApiWithMongoClass {
811811
abortUnshardCollection: ns,
812812
});
813813
}
814+
815+
@apiVersions([])
816+
@returnsPromise
817+
async listShards(): Promise<ShardInfo[]> {
818+
this._emitShardApiCall('listShards', {});
819+
await getConfigDB(this._database);
820+
821+
return (await this._database.adminCommand({ listShards: 1 })).shards ?? [];
822+
}
823+
824+
@serverVersions(['8.0.0', ServerVersions.latest])
825+
@apiVersions([])
826+
@returnsPromise
827+
async isConfigShardEnabled(): Promise<Document> {
828+
this._emitShardApiCall('isConfigShardEnabled', {});
829+
await getConfigDB(this._database);
830+
831+
const shards = (await this._database.adminCommand({ listShards: 1 }))
832+
.shards as Document[] | undefined;
833+
const configShard = shards?.find((s) => s._id === 'config');
834+
if (!configShard) {
835+
return { enabled: false };
836+
}
837+
838+
return {
839+
enabled: true,
840+
host: configShard.host,
841+
...(configShard.tags && { tags: configShard.tags }),
842+
};
843+
}
814844
}

0 commit comments

Comments
 (0)