Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/mongodb-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ layer, managing the storage backend's docker compose project alongside the
mongod processes. See
[docs/disaggregated-storage.md](./docs/disaggregated-storage.md).

Point it at an SLS dir — the `buildscripts/modules/atlas` directory of a
disaggregated-storage-capable MongoDB build (an installed build or a Server
source checkout), or any parent containing it — and everything else is located
automatically:

```bash
# installed build
npx @mongodb-js/mongodb-runner start -t replset --slsDir=/path/to/dsc-install --binDir=...
# Server source checkout
npx @mongodb-js/mongodb-runner start -t replset --slsDir=$MONGO_REPO --binDir=...
```

## License

Apache 2.0
47 changes: 24 additions & 23 deletions packages/mongodb-runner/docs/disaggregated-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,49 @@ override.
- `binDir`: a local directory containing the binaries, or
- `downloadUrl`: a URL to a tarball of such a build (cached by URL, standard
release-tarball layout with a top-level directory containing `bin/`).
- The SLS multi-cell compose file — mongodb-runner does not ship one; point
it at the file you want to use, typically
`buildscripts/modules/atlas/sls-multicell-docker-compose.yml` in a mongodb
server checkout. Files it references (`slsbackup.proto`,
`flags-state.json`) are resolved relative to it, and the services/ports are
parsed from it, so any version of the file works as-is.
- The SLS dir — mongodb-runner does not ship these files. They live in the
`buildscripts/modules/atlas` directory of the 10gen/mongo repo and are:
- `sls-multicell-docker-compose.yml` — the multi-cell compose file.
- `manifest.json` — its `pinned_sls_commit` supplies the default image tag.
- `slsbackup.proto` and `flags-state.json` — files the compose file
references.

Point `slsDir` at it; the runner locates and validates the files and reads
the image tag from the manifest.

- The image tag is read automatically from the `pinned_sls_commit` in
`manifest.json` sitting next to the compose file (the server repository's
`buildscripts/modules/atlas/manifest.json`). Pass `--slsImageTag` to
`manifest.json` sitting next to the compose file. Pass `--slsImageTag` to
override it.

## Quick start

Given the compose file, everything else (image tag, compose environment
Given a disaggregated-storage-capable MongoDB build — an installed build or a
Server source checkout — everything else (image tag, compose environment
variables, readiness polling, per-shard log creation, and the
`disaggregatedStorageConfig` server parameter) is generated automatically.
Full sequence, assuming a mongodb server checkout at `$MONGO_REPO`:
Assuming a build at `$BUILD_DIR`:

```bash
# 1. Log in to the SLS image registry
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 664315256653.dkr.ecr.us-east-1.amazonaws.com

# 2. Start a 2-node replica set backed by SLS (the image tag is read from the
# manifest.json next to the compose file; pass --slsImageTag to override it)
@mongodb-js/mongodb-runner start -t replset \
--slsCompose=$MONGO_REPO/buildscripts/modules/atlas/sls-multicell-docker-compose.yml \
--binDir=/path/to/dsc-mongod/bin \
--debug
# or, instead of --binDir:
# --downloadUrl=https://.../dsc-mongod.tgz
# 2. Start a 2-node replica set backed by SLS
@mongodb-js/mongodb-runner start --topology=replset --slsDir="$BUILD_DIR" \
--binDir=/path/to/dsc-mongod/bin --logDir="$LOG_DIR" --id=tools-dsc
```

`--slsDir` locates and validates the SLS dir and reads the image tag from its
`manifest.json`; for a Server source checkout, point it at the repo root and
the files are found under `buildscripts/modules/atlas`.

This prints the connection string once the cluster is up. To get the allocated
SLS service ports/URIs as structured output, add `--json` and capture stdout to
a file instead of scraping it:

```bash
mongodb-runner start -t replset \
--slsCompose=$MONGO_REPO/buildscripts/modules/atlas/sls-multicell-docker-compose.yml \
--slsDir="$BUILD_DIR" \
--binDir=/path/to/dsc-mongod/bin \
--json > cluster.json
```
Expand Down Expand Up @@ -244,13 +247,11 @@ server parameter for one shard; options: `logId`, `cellMetadataService`,

## CLI use

For an SLS project, `--slsCompose` handles everything (see Quick start);
For an SLS project, `--slsDir` handles everything (see Quick start);
`--slsImageTag` is optional and overrides the tag read from the manifest:

```bash
@mongodb-js/mongodb-runner start -t replset \
--slsCompose=/path/to/sls-multicell-docker-compose.yml \
--binDir=...
@mongodb-js/mongodb-runner start -t replset --slsDir=/path/to/dsc-install --binDir=...
```

Custom (non-SLS) storage backends are only supported through the programmatic
Expand Down
2 changes: 1 addition & 1 deletion packages/mongodb-runner/examples/sls-replset.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//
// This is equivalent to:
// mongodb-runner start -t replset \
// --slsCompose=<path> --binDir=...
// --slsDir=<path> --binDir=...

// Enable mongodb-runner debug output (compose progress, server startup, ...)
// unless the user already configured DEBUG themselves.
Expand Down
27 changes: 27 additions & 0 deletions packages/mongodb-runner/src/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ async function runCli(
return stdout;
}

async function runCliExpectingFailure(args: string[]): Promise<string> {
try {
await runCli(args);
} catch (err) {
return String((err as { stderr?: string }).stderr ?? '');
}
throw new Error(`expected the CLI to fail: ${args.join(' ')}`);
}

describe('cli', function () {
this.timeout(process.platform === 'win32' ? 400_000 : 100_000);
let tmpDir = '';
Expand Down Expand Up @@ -213,4 +222,22 @@ describe('cli', function () {

expect(JSON.parse(stdout)).to.deep.equal([]);
});

it('rejects a --slsDir with no path', async function () {
const stderr = await runCliExpectingFailure(['start', '--slsDir']);
expect(stderr, 'the error should say a path is required').to.include(
'--slsDir requires a path',
);
});

it('rejects a --slsDir that is not an SLS dir', async function () {
const stderr = await runCliExpectingFailure([
'start',
`--slsDir=${path.join(tmpDir, 'not-an-sls-dir')}`,
]);
expect(
stderr,
'a directory without the SLS files should be rejected',
).to.include('not a disaggregated-storage-capable MongoDB build');
});
});
26 changes: 21 additions & 5 deletions packages/mongodb-runner/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ import type { MongoClientOptions } from 'mongodb';
type: 'string',
describe: 'Configure OIDC authentication on the server',
})
.option('slsCompose', {
.option('slsDir', {
type: 'string',
describe:
'Path to an SLS multi-cell docker-compose.yml; launches the SLS DSC project and configures mongod to use it (requires a DSC-capable mongod via --binDir or --downloadUrl)',
'Path to an SLS dir: its buildscripts/modules/atlas directory, or any parent containing it (an installed build or a Server source checkout). Locates the compose file and reads the image tag from the manifest',
})
.option('slsImageTag', {
type: 'string',
describe:
'SLS docker image tag to use with --slsCompose (defaults to the pinned_sls_commit from the manifest.json next to the compose file)',
'SLS docker image tag to use (defaults to the pinned_sls_commit from the manifest.json next to the compose file)',
})
.option('debug', { type: 'boolean', describe: 'Enable debug output' })
.option('verbose', { type: 'boolean', describe: 'Enable verbose output' })
Expand Down Expand Up @@ -135,10 +135,26 @@ import type { MongoClientOptions } from 'mongodb';
);
}

async function resolveSlsComposeFile(): Promise<string | undefined> {
if (argv.slsDir === undefined) {
return undefined;
}
if (!argv.slsDir) {
throw new Error(
'--slsDir requires a path to a directory containing the SLS services docker compose file and other helper files - see the disaggregated storage docs for more details',
);
}
const resolved = await utilities.resolveSLSDir(argv.slsDir, {
requireManifest: !argv.slsImageTag,
});
return resolved.composeFile;
}

async function start() {
const disaggregatedStorage = argv.slsCompose
const composeFile = await resolveSlsComposeFile();
const disaggregatedStorage = composeFile
? await utilities.createSLSDisaggregatedStorageOptions({
composeFile: argv.slsCompose,
composeFile,
imageTag: argv.slsImageTag,
})
: undefined;
Expand Down
4 changes: 4 additions & 0 deletions packages/mongodb-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export {
type SLSDisaggregatedStorageSetupOptions,
parseSLSComposeServices,
readPinnedSlsCommit,
resolveSLSDir,
SLS_ATLAS_SUBDIR,
SLS_HOSTNAME,
SLS_CELL1,
SLS_CELL2,
Expand All @@ -33,6 +35,8 @@ export {
type SLSServiceInfo,
type SLSMultiCellEnvironment,
type SLSMultiCellEnvironmentOptions,
type SLSDir,
type ResolveSLSDirOptions,
} from './sls';
export {
DockerComposeProject,
Expand Down
119 changes: 119 additions & 0 deletions packages/mongodb-runner/src/sls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createSLSDisaggregatedStorageOptions,
createSLSMultiCellEnvironment,
readPinnedSlsCommit,
resolveSLSDir,
} from './sls';
import { allocatePorts, uuid } from './util';
import { isBindable } from '../test/helpers';
Expand Down Expand Up @@ -55,6 +56,124 @@ describe('sls', function () {
});
});

describe('resolveSLSDir', function () {
it('resolves an SLS dir addressed by install root', async function () {
const root = path.join(FIXTURES, 'sls-dir-complete');
const slsDir = await resolveSLSDir(root);
expect(
slsDir.composeFile,
'compose file should be found under buildscripts/modules/atlas',
).to.equal(
path.join(
root,
'buildscripts',
'modules',
'atlas',
'sls-multicell-docker-compose.yml',
),
);
expect(
slsDir.manifestFile,
'manifest should sit next to the compose file',
).to.equal(path.join(slsDir.atlasDir, 'manifest.json'));
});

it('resolves an SLS dir addressed by its atlas directory', async function () {
const atlasDir = path.join(
FIXTURES,
'sls-dir-complete',
'buildscripts',
'modules',
'atlas',
);
const slsDir = await resolveSLSDir(atlasDir);
expect(
slsDir.atlasDir,
'passing the atlas dir directly should also work',
).to.equal(atlasDir);
});

it('lists every missing file, not just the first', async function () {
const err = await resolveSLSDir(
path.join(FIXTURES, 'sls-dir-incomplete'),
).catch((e: Error) => e);
const message = (err as Error).message;
expect(
message,
'error should say this is not a disagg-capable build',
).to.include('not a disaggregated-storage-capable MongoDB build');
expect(message, 'should report the missing proto').to.include(
'slsbackup.proto',
);
expect(message, 'should report the missing flags state too').to.include(
'flags-state.json',
);
});

it('rejects a required file that is actually a directory', async function () {
const err = await resolveSLSDir(
path.join(FIXTURES, 'sls-dir-dir-placeholder'),
).catch((e: Error) => e);
expect(
(err as Error).message,
'a directory named like a required file should be reported missing',
).to.include('slsbackup.proto');
});

it('rejects a directory that is not a MongoDB build', async function () {
const err = await resolveSLSDir(path.join(FIXTURES, 'not-a-build')).catch(
(e: Error) => e,
);
expect(
(err as Error).message,
'a non-build directory should be rejected clearly',
).to.include('not a disaggregated-storage-capable MongoDB build');
});

it('returns absolute paths for a relative directory', async function () {
const relative = path.relative(
process.cwd(),
path.join(FIXTURES, 'sls-dir-complete'),
);
const slsDir = await resolveSLSDir(relative);
expect(
path.isAbsolute(slsDir.composeFile),
'the compose file must be absolute so it survives a change of cwd',
).to.be.true;
expect(
path.isAbsolute(slsDir.atlasDir),
'the atlas dir must be absolute too',
).to.be.true;
});

it('requires a manifest by default', async function () {
const err = await resolveSLSDir(
path.join(FIXTURES, 'sls-dir-no-manifest'),
).catch((e: Error) => e);
expect(
(err as Error).message,
'a missing manifest should be reported by default',
).to.include('manifest.json');
});

it('allows a missing manifest when the tag is supplied', async function () {
const dir = path.join(FIXTURES, 'sls-dir-no-manifest');
const slsDir = await resolveSLSDir(dir, { requireManifest: false });
expect(
slsDir.composeFile,
'the compose file should still be located without a manifest',
).to.equal(
path.join(
dir,
'buildscripts',
'modules',
'atlas',
'sls-multicell-docker-compose.yml',
),
);
});
});

describe('createSLSMultiCellEnvironment', function () {
let tmpDir: string;
let composeFile: string;
Expand Down
Loading
Loading