You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
4.8 KiB

// core-node.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const uuidv4 = require('uuid').v4;
const mongoose = require('mongoose');
const fetch = require('node-fetch'); // jshint ignore:line
const CoreNode = mongoose.model('CoreNode');
const CoreNodeRequest = mongoose.model('CoreNodeRequest');
const { SiteService, SiteError } = require('../../lib/site-lib');
class CoreAddress {
constructor (host, port) {
this.host = host;
this.port = port;
}
parse (host) {
const tokens = host.split(':');
this.host = tokens[0];
if (tokens[1]) {
this.port = parseInt(tokens[1], 10);
} else {
this.port = 443;
}
return this;
}
}
class CoreNodeService extends SiteService {
constructor (dtp) {
super(dtp, module.exports);
}
parseCoreAddress (host) {
const address = new CoreAddress();
return address.parse(host);
}
async getCoreByAddress (address) {
const core = await CoreNode
.findOne({
'address.host': address.host,
'address.port': address.port,
})
.lean();
return core;
}
/**
* First ensures that a record exists in the local database for the Core node.
* Then, calls the node's info services to resolve more metadata about the
* node, it's operation, policies, and available services.
*
* @param {String} host hostname and optional port number of Core node to be
* resolved.
* @returns CoreNode document describing the Core node.
*/
async resolveCore (host) {
const NOW = new Date();
this.log.info('resolving Core node', { host });
const address = this.parseCoreAddress(host);
let core = await this.getCoreByAddress(address);
if (!core) {
core = new CoreNode();
core.created = NOW;
core.address.host = address.host;
core.address.port = address.port;
await core.save();
core = core.toObject();
}
const txSite = await this.sendRequest(core, {
method: 'GET',
url: '/core/info/site',
});
const txPackage = await this.sendRequest(core, {
method: 'GET',
url: '/core/info/package',
});
await CoreNode.updateOne(
{ _id: core._id },
{
$set: {
updated: NOW,
'meta.name': txSite.response.site.name,
'meta.description': txSite.response.site.description,
'meta.domain': txSite.response.site.domain,
'meta.domainKey': txSite.response.site.domainKey,
'meta.version': txPackage.response.package.version,
'meta.admin': txSite.response.site.admin,
'meta.supportEmail': txSite.response.site.supportEmail,
},
},
);
core = await CoreNode.findOne({ _id: core._id }).lean();
this.log.info('resolved Core node', { core });
return core;
}
async broadcast (request) {
const results = [ ];
await CoreNode
.find()
.cursor()
.eachAsync(async (core) => {
try {
const response = await this.sendRequest(core, request);
results.push({ coreId: core._id, request, response });
} catch (error) {
this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error });
}
});
return results;
}
async sendRequest (core, request) {
const coreScheme = process.env.DTP_CORE_AUTH_SCHEME || 'https';
const requestUrl = `${coreScheme}://${core.address.host}:${core.address.port}${request.url}`;
try {
const req = new CoreNodeRequest();
req.created = new Date();
req.core = core._id;
req.token = {
value: uuidv4(),
claimed: false,
};
req.method = request.method || 'GET';
req.url = request.url;
await req.save();
this.log.info('sending Core node request', { request: req });
const response = await fetch(requestUrl, {
method: req.method,
body: request.body,
});
const json = await response.json();
/*
* capture a little inline health monitoring data, which can be used to
* generate health alerts.
*/
const DONE = new Date();
const ELAPSED = DONE.valueOf() - req.created.valueOf();
await CoreNodeRequest.updateOne(
{ _id: req._id },
{
$set: {
'response.received': DONE,
'response.elapsed': ELAPSED,
'response.success': json.success,
},
},
);
this.log.info('Core node request complete', { request: req });
return { request: req.toObject(), response: json };
} catch (error) {
this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error });
throw error;
}
}
}
module.exports = {
slug: 'core-node',
name: 'coreNode',
create: (dtp) => { return new CoreNodeService(dtp); },
};