Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Session.vim
scripts/*.js
!scripts/frontendScripts.js
!scripts/jestTest.js
!scripts/setupMongo.js
!scripts/startDatabase.js
database/*.js
*.log
*-debug.log*
Expand Down
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
},
{
"label": "run-mongo",
"command": "mongod",
"command": "npm",
"type": "process",
"args": ["--dbpath", "${workspaceFolder}/mongo_database"],
"args": ["run", "database"],
"problemMatcher": "$tsc"
}
]
Expand Down
4 changes: 2 additions & 2 deletions Backend/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"MongoDB": {
"ConnectionString": "mongodb://localhost:27017",
"ContainerConnectionString": "mongodb://database:27017",
"ConnectionString": "mongodb://localhost:27017/?replicaSet=rs0",
"ContainerConnectionString": "mongodb://database:27017/?replicaSet=rs0",
"CombineDatabase": "CombineDatabase"
},
"Logging": {
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ npm run license-report-frontend

To browse the database locally during development, open [MongoDB Compass](https://www.mongodb.com/try/download/compass).

1. Under New Connection, enter `mongodb://localhost:27017`
1. Under New Connection, enter `mongodb://localhost:27017/?replicaSet=rs0`
2. Under Databases, select CombineDatabase

### Add or Update Dictionary Files
Expand Down
31 changes: 31 additions & 0 deletions database/init/00-replica-set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Initialize the replica set on first startup.
// See: https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-set/
//
// MONGO_INITDB_REPLICA_HOST can be set to the resolvable hostname:port
// used to advertise this member (e.g. the Kubernetes Service name "database:27017").
// It defaults to "localhost:27017" for local development.
try {
rs.status();
} catch (e) {
print(`Replica set not yet initialized (${e}), initializing now...`);
const host = process.env.MONGO_INITDB_REPLICA_HOST || "localhost:27017";
rs.initiate({ _id: "rs0", members: [{ _id: 0, host: host }] });
// Wait for replica set to reach PRIMARY state before other init scripts run.
const maxWaitMs = 30000;
const intervalMs = 500;
let waited = 0;
let isPrimary = false;
while (!isPrimary && waited < maxWaitMs) {
sleep(intervalMs);
waited += intervalMs;
const status = rs.status();
isPrimary =
status.members !== undefined &&
status.members.some((m) => m.stateStr === "PRIMARY");
}
if (!isPrimary) {
throw new Error(
`Replica set did not reach PRIMARY state after ${maxWaitMs}ms`
);
}
}
29 changes: 29 additions & 0 deletions deploy/helm/thecombine/charts/database/templates/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ spec:
- image: {{ include "database.containerImage" . }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
name: database
args:
- "--replSet"
- "rs0"
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
set -e
max_attempts=60
attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
if mongosh --quiet --host 127.0.0.1 --eval "const host = '${MONGO_INITDB_REPLICA_HOST}'; try { rs.status(); const conf = rs.conf(); if (conf.members[0].host !== host) { print('Updating replica set member host to ' + host); conf.members[0].host = host; conf.version++; rs.reconfig(conf); } else { print('Replica set already initialized with correct host'); } } catch (e) { print('Initializing replica set (' + e + ')'); rs.initiate({_id: 'rs0', members: [{_id: 0, host: host}]}); }" ; then
exit 0
fi
sleep 1
attempt=$((attempt + 1))
done
echo "Failed to ensure replica set initialization after ${max_attempts} attempts"
exit 1
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MONGO_INITDB_REPLICA_HOST
value: "$(POD_IP):27017"
ports:
- containerPort: 27017
resources:
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
"backend": "dotnet watch --project Backend/BackendFramework.csproj",
"build": "parcel build",
"build:analyze": "npm run build -- --reporter @parcel/reporter-bundle-analyzer",
"predatabase": "node scripts/setupMongo.js",
"database": "mongod --dbpath=./mongo_database",
"database": "node scripts/startDatabase.js",
"drop-database": "tsc scripts/dropDB.ts && node scripts/dropDB.js",
"find-circular-deps": "npx --ignore-scripts -y madge -c src/index.tsx --ts-config tsconfig.json",
"fmt-backend": " dotnet format && dotnet format Backend.Tests",
Expand Down
7 changes: 0 additions & 7 deletions scripts/setupMongo.js

This file was deleted.

89 changes: 89 additions & 0 deletions scripts/startDatabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use strict";

const { spawn, spawnSync } = require("child_process");
const { ensureDir } = require("fs-extra");

const dbPath = "./mongo_database";
const replSetName = "rs0";
const maxAttempts = 30;
const retryInterval = 1000; // ms

async function waitForMongo() {
for (let i = 0; i < maxAttempts; i++) {
const result = spawnSync("mongosh", [
"--eval",
"db.adminCommand('ping')",
"--quiet",
]);
if (result.status === 0) {
return true;
}
await new Promise((resolve) => setTimeout(resolve, retryInterval));
}
return false;
}

async function initReplicaSet() {
const result = spawnSync(
"mongosh",
[
"--eval",
"try { rs.status() } catch(e) { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] }) }",
"--quiet",
],
{ stdio: "inherit" }
);
return result.status === 0;
}

async function waitForMongoToCloseThenExit(mongod) {
const exitCode = await new Promise((resolve) =>
mongod.on("close", (code) => resolve(code))
);
process.exit(exitCode ?? 1);
}

async function main() {
await ensureDir(dbPath);

const mongod = spawn(
"mongod",
[`--dbpath=${dbPath}`, "--replSet", replSetName],
{
stdio: "inherit",
}
);

mongod.on("error", (err) => {
console.error(`mongod error: ${err.message}`);
process.exit(1);
});

process.on("SIGINT", () => {
mongod.kill("SIGINT");
});
process.on("SIGTERM", () => {
mongod.kill("SIGTERM");
});

const ready = await waitForMongo();
if (!ready) {
console.error("MongoDB did not start in time");
mongod.kill("SIGTERM");
await waitForMongoToCloseThenExit(mongod);
}

const initialized = await initReplicaSet();
if (!initialized) {
console.error("Replica set initialization failed");
mongod.kill("SIGTERM");
await waitForMongoToCloseThenExit(mongod);
}

await waitForMongoToCloseThenExit(mongod);
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
Loading