mirror of
https://github.com/nasa/openmct.git
synced 2025-02-04 10:10:43 +00:00
working version
This commit is contained in:
parent
68436a9d38
commit
a599d874ea
@ -215,8 +215,41 @@ This will create a root object with the id of `mine` in both namespaces upon loa
|
|||||||
5. All done! 🏆
|
5. All done! 🏆
|
||||||
|
|
||||||
# Maintenance
|
# Maintenance
|
||||||
|
All the scripts in this section must be run within this directory (i.e., `src/plugins/persistence/couch`)
|
||||||
|
|
||||||
One can delete annotations by running inside this directory (i.e., `src/plugins/persistence/couch`):
|
## Backing Up
|
||||||
|
One can backup a CouchDB installation by running:
|
||||||
|
```
|
||||||
|
npm run backup:openmct
|
||||||
|
```
|
||||||
|
Note you will need to modify `package.json` to ensure the URL and authorization is correct.
|
||||||
|
|
||||||
|
## Restoring to a New CouchDB Database
|
||||||
|
One can restore to a new (empty) CouchDB database by running
|
||||||
|
```
|
||||||
|
npm run restore:openmct
|
||||||
|
```
|
||||||
|
Note you will need to modify `package.json` to ensure the URL and authorization is correct.
|
||||||
|
|
||||||
|
# Restoring/Updating an Existing CouchDB database
|
||||||
|
One can restore or update an existing CouchDB database by running:
|
||||||
|
```
|
||||||
|
npm run upsert:openmct -- --dbName SOME_DB_NAME --backupFilename /path/to/backup.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the backup file is a JSON file generated from the previously mentioned script. Running this
|
||||||
|
will take every Open MCT object in the backup, and either insert it as new, or if the object already
|
||||||
|
exists, update it with the backup version. Note this script does not restore design documents or other
|
||||||
|
non Open MCT objects.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run upsert:openmct -- --help
|
||||||
|
```
|
||||||
|
|
||||||
|
will print help options.
|
||||||
|
|
||||||
|
## Deleting Annotations
|
||||||
|
One can delete annotations by running:
|
||||||
```
|
```
|
||||||
npm run deleteAnnotations:openmct:PIXEL_SPATIAL
|
npm run deleteAnnotations:openmct:PIXEL_SPATIAL
|
||||||
```
|
```
|
||||||
@ -235,7 +268,6 @@ npm run deleteAnnotations:openmct -- --help
|
|||||||
|
|
||||||
will print help options.
|
will print help options.
|
||||||
# Search Performance
|
# Search Performance
|
||||||
|
|
||||||
For large Open MCT installations, it may be helpful to add additional CouchDB capabilities to bear to improve performance.
|
For large Open MCT installations, it may be helpful to add additional CouchDB capabilities to bear to improve performance.
|
||||||
|
|
||||||
## Indexing
|
## Indexing
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"backup:openmct": "npx couchbackup -u http://admin:password@127.0.0.1:5984/ -d openmct -o openmct-couch-backup.txt",
|
"backup:openmct": "npx couchbackup -u http://admin:password@127.0.0.1:5984/ -d openmct -o openmct-couch-backup.txt",
|
||||||
"restore:openmct": "cat openmct-couch-backup.txt | npx couchrestore -u http://admin:password@127.0.0.1:5984/ -d openmct",
|
"restore:openmct": "cat openmct-couch-backup.txt | npx couchrestore -u http://admin:password@127.0.0.1:5984/ -d openmct",
|
||||||
|
"upsert:openmct": "node scripts/upsert.js $*",
|
||||||
"deleteAnnotations:openmct": "node scripts/deleteAnnotations.js $*",
|
"deleteAnnotations:openmct": "node scripts/deleteAnnotations.js $*",
|
||||||
"deleteAnnotations:openmct:NOTEBOOK": "node scripts/deleteAnnotations.js -- --annotationType NOTEBOOK",
|
"deleteAnnotations:openmct:NOTEBOOK": "node scripts/deleteAnnotations.js -- --annotationType NOTEBOOK",
|
||||||
"deleteAnnotations:openmct:GEOSPATIAL": "node scripts/deleteAnnotations.js -- --annotationType GEOSPATIAL",
|
"deleteAnnotations:openmct:GEOSPATIAL": "node scripts/deleteAnnotations.js -- --annotationType GEOSPATIAL",
|
||||||
|
@ -37,6 +37,11 @@ async function main() {
|
|||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
});
|
});
|
||||||
|
if (!docsToDelete.length) {
|
||||||
|
console.info('🤷♂️ No annotations found to delete on server');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const deletedDocumentCount = await performBulkDelete({
|
const deletedDocumentCount = await performBulkDelete({
|
||||||
docsToDelete,
|
docsToDelete,
|
||||||
serverUrl,
|
serverUrl,
|
||||||
@ -44,8 +49,8 @@ async function main() {
|
|||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
});
|
});
|
||||||
console.log(
|
console.info(
|
||||||
`Deleted ${deletedDocumentCount} document${deletedDocumentCount === 1 ? '' : 's'}.`
|
`🎉 Deleted ${deletedDocumentCount} document${deletedDocumentCount === 1 ? '' : 's'}.`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error: ${error.message}`);
|
console.error(`Error: ${error.message}`);
|
||||||
@ -66,14 +71,15 @@ function processArguments() {
|
|||||||
let databaseName = 'openmct'; // default db name to "openmct"
|
let databaseName = 'openmct'; // default db name to "openmct"
|
||||||
let serverUrl = new URL('http://127.0.0.1:5984'); // default db name to "openmct"
|
let serverUrl = new URL('http://127.0.0.1:5984'); // default db name to "openmct"
|
||||||
let helpRequested = false;
|
let helpRequested = false;
|
||||||
|
console.debug = () => {};
|
||||||
|
|
||||||
args.forEach((val, index) => {
|
args.forEach((val, index) => {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case '--help':
|
case '--help':
|
||||||
console.log(
|
console.info(
|
||||||
'Usage: deleteAnnotations.js [--annotationType type] [--dbName name] <CouchDB URL> \nFor authentication, set the environment variables COUCHDB_USERNAME and COUCHDB_PASSWORD. \n'
|
'Usage: deleteAnnotations.js [--annotationType type] [--dbName name] <CouchDB URL> \nFor authentication, set the environment variables COUCHDB_USERNAME and COUCHDB_PASSWORD. \n'
|
||||||
);
|
);
|
||||||
console.log('Annotation types: ', Object.keys(ANNOTATION_TYPES).join(', '));
|
console.info('Annotation types: ', Object.keys(ANNOTATION_TYPES).join(', '));
|
||||||
helpRequested = true;
|
helpRequested = true;
|
||||||
break;
|
break;
|
||||||
case '--annotationType':
|
case '--annotationType':
|
||||||
@ -88,6 +94,9 @@ function processArguments() {
|
|||||||
case '--serverUrl':
|
case '--serverUrl':
|
||||||
serverUrl = new URL(args[index + 1]);
|
serverUrl = new URL(args[index + 1]);
|
||||||
break;
|
break;
|
||||||
|
case '--debug':
|
||||||
|
console.debug = console.log;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,9 +150,14 @@ async function gatherDocumentsForDeletion({
|
|||||||
findOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
findOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.info(`🛜 Contacting ${baseUrl} to find annotations to delete`);
|
||||||
while (hasMoreDocs) {
|
while (hasMoreDocs) {
|
||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
body.bookmark = bookmark;
|
console.debug(`Server has more documents to process, fetching more...`);
|
||||||
|
findOptions.body = JSON.stringify({
|
||||||
|
...body,
|
||||||
|
bookmark
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(baseUrl, findOptions);
|
const res = await fetch(baseUrl, findOptions);
|
||||||
@ -159,7 +173,11 @@ async function gatherDocumentsForDeletion({
|
|||||||
|
|
||||||
// check if we got less than limit, set hasMoreDocs to false
|
// check if we got less than limit, set hasMoreDocs to false
|
||||||
hasMoreDocs = findResult.docs.length === body.limit;
|
hasMoreDocs = findResult.docs.length === body.limit;
|
||||||
|
console.debug(
|
||||||
|
`Fetched ${docsToDelete.length} documents so far, and find result has ${findResult.docs.length} documents`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
console.debug(`Found ${docsToDelete.length} existing annotations on ${baseUrl}`);
|
||||||
|
|
||||||
return docsToDelete;
|
return docsToDelete;
|
||||||
}
|
}
|
||||||
@ -179,7 +197,10 @@ async function performBulkDelete({ docsToDelete, serverUrl, databaseName, userna
|
|||||||
deleteOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
deleteOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${serverUrl.href}${databaseName}/_bulk_docs`, deleteOptions);
|
const baseUrl = `${serverUrl.href}${databaseName}/_bulk_docs`;
|
||||||
|
|
||||||
|
console.info(`🛜 Contacting ${baseUrl} to delete ${docsToDelete.length} annotations`);
|
||||||
|
const response = await fetch(baseUrl, deleteOptions);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed with status code: ' + response.status);
|
throw new Error('Failed with status code: ' + response.status);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
@ -30,16 +31,13 @@ async function main() {
|
|||||||
if (helpRequested) {
|
if (helpRequested) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const restoredDocumentCount = await performUpsert({
|
await performUpsert({
|
||||||
backupFilename,
|
backupFilename,
|
||||||
serverUrl,
|
serverUrl,
|
||||||
databaseName,
|
databaseName,
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
});
|
});
|
||||||
console.log(
|
|
||||||
`Restored ${restoredDocumentCount} document${restoredDocumentCount === 1 ? '' : 's'}.`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error: ${error.message}`);
|
console.error(`Error: ${error.message}`);
|
||||||
}
|
}
|
||||||
@ -48,15 +46,16 @@ async function main() {
|
|||||||
function processArguments() {
|
function processArguments() {
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
let databaseName = 'openmct'; // default db name to "openmct"
|
let databaseName = 'openmct'; // default db name to "openmct"
|
||||||
let serverUrl = new URL('http://127.0.0.1:5984'); // default db name to "openmct"
|
let serverUrl = new URL('http://127.0.0.1:5984/'); // default db name to "openmct"
|
||||||
let backupFilename = 'backup.json'; // default backup filename to "backup.json"
|
let backupFilename = 'backup.json'; // default backup filename to "backup.json"
|
||||||
let helpRequested = false;
|
let helpRequested = false;
|
||||||
|
console.debug = () => {};
|
||||||
|
|
||||||
args.forEach((val, index) => {
|
args.forEach((val, index) => {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case '--help':
|
case '--help':
|
||||||
console.log(
|
console.log(
|
||||||
'Usage: restore.js [--backupFilename pathToBackupJSON] [--dbName name] <CouchDB URL> \nFor authentication, set the environment variables COUCHDB_USERNAME and COUCHDB_PASSWORD. \n'
|
'Usage: restore.js [--backupFilename pathToBackupJSON] [--dbName name] [--debug] <CouchDB URL> \nFor authentication, set the environment variables COUCHDB_USERNAME and COUCHDB_PASSWORD. \n'
|
||||||
);
|
);
|
||||||
helpRequested = true;
|
helpRequested = true;
|
||||||
break;
|
break;
|
||||||
@ -65,10 +64,16 @@ function processArguments() {
|
|||||||
break;
|
break;
|
||||||
case '--serverUrl':
|
case '--serverUrl':
|
||||||
serverUrl = new URL(args[index + 1]);
|
serverUrl = new URL(args[index + 1]);
|
||||||
|
if (!serverUrl.href.endsWith('/')) {
|
||||||
|
serverUrl.href += '/';
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '--backupFilename':
|
case '--backupFilename':
|
||||||
backupFilename = args[index + 1];
|
backupFilename = args[index + 1];
|
||||||
break;
|
break;
|
||||||
|
case '--debug':
|
||||||
|
console.debug = console.log;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -110,10 +115,15 @@ async function getExistingDocsIdToRevMap({ serverUrl, databaseName, username, pa
|
|||||||
if (username && password) {
|
if (username && password) {
|
||||||
findOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
findOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||||
}
|
}
|
||||||
|
console.info(`🛜 Contacting ${baseUrl} to find existing document revisions`);
|
||||||
|
|
||||||
while (hasMoreDocs) {
|
while (hasMoreDocs) {
|
||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
body.bookmark = bookmark;
|
console.debug(`Server has more documents to process, fetching more...`);
|
||||||
|
findOptions.body = JSON.stringify({
|
||||||
|
...body,
|
||||||
|
bookmark
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(baseUrl, findOptions);
|
const res = await fetch(baseUrl, findOptions);
|
||||||
@ -129,20 +139,104 @@ async function getExistingDocsIdToRevMap({ serverUrl, databaseName, username, pa
|
|||||||
|
|
||||||
// check if we got less than limit, set hasMoreDocs to false
|
// check if we got less than limit, set hasMoreDocs to false
|
||||||
hasMoreDocs = findResult.docs.length === body.limit;
|
hasMoreDocs = findResult.docs.length === body.limit;
|
||||||
|
console.debug(
|
||||||
|
`Fetched ${existingDocs.length} documents so far, and find result has ${findResult.docs.length} documents`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.debug(`Found ${existingDocs.length} existing documents on ${baseUrl}`);
|
||||||
|
|
||||||
|
// transform existinDocs to a map of id to rev
|
||||||
|
const idToRevMap = existingDocs.reduce((acc, doc) => {
|
||||||
|
acc[doc._id] = doc._rev;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return idToRevMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
return existingDocs;
|
async function prepareBackupDocuments({ backupFilename, existingDocsidToRevMap }) {
|
||||||
|
// load backup file
|
||||||
|
const rawBackup = await fs.readFile(backupFilename);
|
||||||
|
const backupJSON = JSON.parse(rawBackup);
|
||||||
|
|
||||||
|
console.debug(`${backupJSON.length} backup documents found in ${backupFilename}`);
|
||||||
|
let newDocumentsToAdd = 0;
|
||||||
|
let existingDocumentsToUpdate = 0;
|
||||||
|
const docsToRestore = [];
|
||||||
|
backupJSON.forEach((backupCouchDocument) => {
|
||||||
|
if (backupCouchDocument.model && backupCouchDocument._id) {
|
||||||
|
const existingDocRev = existingDocsidToRevMap[backupCouchDocument._id];
|
||||||
|
const docToRestore = {
|
||||||
|
model: backupCouchDocument.model,
|
||||||
|
_id: backupCouchDocument._id
|
||||||
|
};
|
||||||
|
if (existingDocRev) {
|
||||||
|
// need to add rev for couchdb to update
|
||||||
|
docToRestore._rev = existingDocRev;
|
||||||
|
existingDocumentsToUpdate++;
|
||||||
|
} else {
|
||||||
|
newDocumentsToAdd++;
|
||||||
|
}
|
||||||
|
docsToRestore.push(docToRestore);
|
||||||
|
} else {
|
||||||
|
console.debug(`Skipping non-model document: ${backupCouchDocument._id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { docsToRestore, newDocumentsToAdd, existingDocumentsToUpdate };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performUpsert({ backupFilename, serverUrl, databaseName, username, password }) {
|
async function performUpsert({ backupFilename, serverUrl, databaseName, username, password }) {
|
||||||
console.debug(`👾 Contacting ${serverUrl}/${databaseName}`);
|
const existingDocsidToRevMap = await getExistingDocsIdToRevMap({
|
||||||
const existingDocs = await getExistingDocsIdToRevMap({
|
|
||||||
serverUrl,
|
serverUrl,
|
||||||
databaseName,
|
databaseName,
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
});
|
});
|
||||||
console.debug('🐻 Existing docs:', existingDocs);
|
|
||||||
|
const { docsToRestore, newDocumentsToAdd, existingDocumentsToUpdate } =
|
||||||
|
await prepareBackupDocuments({
|
||||||
|
backupFilename,
|
||||||
|
existingDocsidToRevMap
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoreOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ docs: docsToRestore })
|
||||||
|
};
|
||||||
|
if (username && password) {
|
||||||
|
restoreOptions.headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||||
|
}
|
||||||
|
const baseUrl = `${serverUrl.href}${databaseName}/_bulk_docs`;
|
||||||
|
console.info(
|
||||||
|
`🛜 Contacting ${baseUrl} to add ${newDocumentsToAdd} new documents, and update ${existingDocumentsToUpdate} existing documents`
|
||||||
|
);
|
||||||
|
const restoreResponse = await fetch(baseUrl, restoreOptions);
|
||||||
|
|
||||||
|
if (!restoreResponse.ok) {
|
||||||
|
throw new Error(`Server responded with status: ${restoreResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreJsonResult = await restoreResponse.json();
|
||||||
|
let restorationErrorCount = 0;
|
||||||
|
let restorationSuccessCount = 0;
|
||||||
|
restoreJsonResult.forEach((result) => {
|
||||||
|
if (result.error) {
|
||||||
|
console.error(`🚨 ${result.error} restoring document ${result.id} due to: ${result.reason}`);
|
||||||
|
restorationErrorCount++;
|
||||||
|
} else {
|
||||||
|
restorationSuccessCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (restorationErrorCount > 0) {
|
||||||
|
console.error(`🚨 ${restorationErrorCount} documents failed to restore`);
|
||||||
|
}
|
||||||
|
if (restorationSuccessCount > 0) {
|
||||||
|
console.info(`🎉 ${restorationSuccessCount} documents restored successfully`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
Loading…
x
Reference in New Issue
Block a user