From d063a4ebd4bb52c786d388dce0a3d9c8237a9281 Mon Sep 17 00:00:00 2001 From: Massimo Candela Date: Fri, 21 May 2021 15:24:56 +0200 Subject: [PATCH] working pull api --- config.yml.example | 4 + src/config/config.js | 4 + src/inputs/input.js | 2 +- src/processMonitors/uptimeApi.js | 25 +++--- src/reports/reportPullAPI.js | 128 +++++++++++++++++++++++++++++++ src/utils/restApi.js | 63 +++++++++++++++ 6 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 src/reports/reportPullAPI.js create mode 100644 src/utils/restApi.js diff --git a/config.yml.example b/config.yml.example index e9050d1..415ee36 100644 --- a/config.yml.example +++ b/config.yml.example @@ -241,6 +241,10 @@ reports: notificationIntervalSeconds: 86400 persistStatus: true +rest: + host: null + port: 8011 + logging: directory: logs logRotatePattern: YYYY-MM-DD diff --git a/src/config/config.js b/src/config/config.js index 9e875ad..95114be 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -111,6 +111,10 @@ export default class Config { preCacheROAs: true, refreshVrpListMinutes: 15 }, + rest: { + host: null, + port: 8011 + }, checkForUpdatesAtBoot: true, pidFile: "bgpalerter.pid", fadeOffSeconds: 360, diff --git a/src/inputs/input.js b/src/inputs/input.js index 5c8422b..96f8cd0 100644 --- a/src/inputs/input.js +++ b/src/inputs/input.js @@ -53,7 +53,7 @@ export default class Input { // This implements a fast basic fixed space cache, other approaches lru-like use too much cpu setInterval(() => { if (Object.keys(this.cache.matched).length > 10000) { - delete this.cache.matched; + this.cache.matched = {}; } }, 10000); diff --git a/src/processMonitors/uptimeApi.js b/src/processMonitors/uptimeApi.js index 26fc46a..e1405f5 100644 --- a/src/processMonitors/uptimeApi.js +++ b/src/processMonitors/uptimeApi.js @@ -31,8 +31,8 @@ */ import Uptime from "./uptime"; -import restify from "restify"; import env from "../env"; +import RestApi from "../utils/restApi"; export default class UptimeApi extends Uptime { @@ -40,23 +40,16 @@ export default class UptimeApi extends Uptime { super(connectors, params); this.server = null; this.connectors = connectors; + let restDefault = env.config.rest || { port: params.port, host: params.host }; + const rest = new RestApi(restDefault); - try { - this.server = restify.createServer(); - this.server.pre(restify.pre.sanitizePath()); - this.server.get('/status', this.respond); - this.server.head('/status', this.respond); - if (params.host) { - this.server.listen(params.port, params.host); - } else { - this.server.listen(params.port); - } - } catch (error) { - env.logger.log({ - level: 'error', - message: error + rest.addUrl('/status', this.respond) + .catch(error => { + env.logger.log({ + level: 'error', + message: error + }); }); - } }; respond = (req, res, next) => { diff --git a/src/reports/reportPullAPI.js b/src/reports/reportPullAPI.js new file mode 100644 index 0000000..d468869 --- /dev/null +++ b/src/reports/reportPullAPI.js @@ -0,0 +1,128 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2019, NTT Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Report from "./report"; +import RestApi from "../utils/restApi"; +import md5 from "md5"; +import moment from "moment"; + +export default class ReportPullAPI extends Report { + + constructor(channels, params, env) { + super(channels, params, env); + + this.name = "reportPullAPI" || this.params.name; + this.enabled = true; + this.maxAlertsAmount = 100; + this.lastCheck = null; + + let restDefault = env.config.rest || { port: params.port, host: params.host }; + const rest = new RestApi(restDefault); + + rest.addUrl('/alerts', this.respond) + .catch(error => { + env.logger.log({ + level: 'error', + message: error + }); + }); + + rest.addUrl('/alerts/:hash', this.respond) + .catch(error => { + env.logger.log({ + level: 'error', + message: error + }); + }); + + rest.addUrl('/alerts/groups/:group', this.respond) + .catch(error => { + env.logger.log({ + level: 'error', + message: error + }); + }); + + this.alerts = []; + }; + + respond = (req, res, next) => { + res.contentType = 'json'; + res.send({ + meta: { + lastCheck: this.lastCheck + }, + data: this._getAlerts(req.params) + }); + next(); + this.lastCheck = moment(new Date()).utc(); + }; + + _getAlerts = ({ hash, group }) => { + let alerts; + + if (group) { + alerts = this.alerts.filter(i => i.group === group); + } else if (hash) { + const mid = md5(hash); + alerts = this.alerts.filter(i => i.alert.hash === mid); + } else { + alerts = this.alerts; + } + + return alerts.map(i => i.alert); + } + + getUserGroup = (group) => { + const groups = this.params.hooks || this.params.userGroups; + + return groups[group] || groups["default"]; + }; + + report = (channel, content) => { + if (this.enabled) { + let groups = content.data.map(i => i.matchedRule.group).filter(i => i != null); + + groups = (groups.length) ? [...new Set(groups)] : [this.getUserGroup("default")]; + content.hash = md5(content.id); + + for (let group of groups) { + this.alerts.push({ + group, + alert: content + }); + this.alerts = this.alerts.slice(-this.maxAlertsAmount); + } + + } + }; +} \ No newline at end of file diff --git a/src/utils/restApi.js b/src/utils/restApi.js new file mode 100644 index 0000000..4903c46 --- /dev/null +++ b/src/utils/restApi.js @@ -0,0 +1,63 @@ +import restify from "restify"; + +export default class RestApi { + static _instance; + + constructor(params) { + + this.params = params; + this.port = this.params.port || 8011; + this.host = this.params.host || null; + this.enabled = false; + this.urls = {}; + this._serverPromise = null; + + if (!!RestApi._instance) { + return RestApi._instance; + } + + RestApi._instance = this; + } + + _startServer = () => { + if (!this._serverPromise) { + this._serverPromise = new Promise((resolve, reject) => { + try { + if (this.host && this.port) { + this.server = restify.createServer(); + this.server.pre(restify.pre.sanitizePath()); + this.server.listen(this.port, this.host); + this.enabled = true; + resolve(); + } else if (this.port) { + this.server = restify.createServer(); + this.server.pre(restify.pre.sanitizePath()); + this.server.listen(this.port); + this.enabled = true; + resolve(); + } else { + this.enabled = false + reject("The port parameter must be specified to start the REST API.") + } + } catch (error) { + this.enabled = false; + reject(error); + } + }); + } + + return this._serverPromise; + }; + + addUrl = (url, callback) => { + if (this.urls[url]) { + return Promise.reject("The URL for the REST API already exists and cannot be replaced"); + } else { + this.urls[url] = callback; + return this._startServer() + .then(() => { + this.server.get(url, this.urls[url]); + }) + } + } +} \ No newline at end of file