Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate monitor.sh #53

Merged
merged 4 commits into from
Jan 5, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

To help monitor network health.

Exit nodes periodically execute [monitor.sh](./monitor.sh) and [post-routing-table.sh](./post-routing-table.sh), hitting two API endpoints at https://peoplesopen.herokuapp.com/api/v0/. This relays information about the number of active routes, active gateways, and the full contents of the exit node's routing table. If an exit node hasn't checked in in more than 2 minutes, it is assumed to be down.
Exit nodes periodically execute [post-routing-table.sh](./post-routing-table.sh), hitting an API endpoint at https://peoplesopen.herokuapp.com/api/v0/nodes. This relays information about the number of active routes, active gateways, and the full contents of the exit node's routing table. If an exit node hasn't checked in in more than 2 minutes, it is assumed to be down.

Uses memcache/memjs, and mongo db. Deployed to heroku.

Expand Down
14 changes: 0 additions & 14 deletions monitor.sh

This file was deleted.

28 changes: 2 additions & 26 deletions spec/httpSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const supertest = require('supertest');
const MonitorApp = require('../src/app').MonitorApp
const MonitorApp = require('../src/app').MonitorApp;
const getDBConnection = require('../src/db');
const exitnodeIPs = require('../src/exitnodeIPs');

describe('HTTP tests', function() {

Expand Down Expand Up @@ -31,27 +30,4 @@ describe('HTTP tests', function() {
.expect(404, done);
});
});

describe('POST /api/v0/monitor', function () {
it('error on non-exit node', function (done) {
supertest(MonitorApp({db}))
.post('/api/v0/monitor')
.accept('Content-Type', 'application/json')
.expect({ "error": "You aren\'t an exit node."})
.expect(403, done);
});
});

describe('POST /api/v0/monitor', function () {
it('error on malformed exit node', function (done) {
let app = MonitorApp({db});
supertest(app)
.post('/api/v0/monitor')
.set('x-forwarded-for', exitnodeIPs[0])
.accept('Content-Type', 'application/json')
.expect({ error: 'Bad request' })
.expect(400, done);
});
});

});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yesssssssssssss

});
60 changes: 38 additions & 22 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,44 @@ function MonitorApp ({
}

async function getMonitorUpdates() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make this function name more specific while we're here. Could be something like your inner helper function -- latestExitnodeStats(). Or numNodesLastSeenAtExitnodes().

Copy link
Collaborator Author

@haydenhw haydenhw Jan 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up renaming this to countActiveRoutesByExitnode(). Let me know if you like one of these other names better. Happy to change it to whatever

return await Promise.all(exitnodeIPs.map(async ip => {
let d = await getCacheData(`alive-${ip}`);
d = d ? d : { error: util.noCheckInMessage(ip) };
d.ip = ip;
return d;
// Report a count of unique nodes and gateways connected to exitnodes
// If an exitnode has not checked in in the last two minutes, report an error message
const getExitnodeStats = function (exitnodeIP) {
return db.collection('routeLog')
.aggregate([
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is an aggregation necessary here? I'm wondering if it would be simpler to fetch the most recent entry in the log for an exitnodeIP, and then count the number of nodes/gateways outside of mongo-land.

{
// Get route logs posted in the last two minutes
$match: {
exitnodeIP,
timestamp: { $gt: new Date(new Date() - 2 * 60 * 1000) },
},
},
{ $sort: { timestamp: -1 } },
// Select only the most recent log
{ $limit: 1 } ,
{ $unwind: '$routes' },
{
$group: {
_id: '$exitnodeIP',
nodeIPs: { $addToSet: '$routes.nodeIP' },
gatewayIPs: { $addToSet: '$routes.gatewayIP' }
}
},
{
$project: {
ip: exitnodeIP,
numberOfRoutes: { $size: '$nodeIPs' },
numberOfGateways: { $size: '$gatewayIPs' },
_id: false,
}
}
])
.toArray();
};

return Promise.all(exitnodeIPs.map(async ip => {
const [stats] = await getExitnodeStats(ip);
return stats || { error: util.noCheckInMessage(ip) };
}));
}

Expand Down Expand Up @@ -119,23 +152,6 @@ function MonitorApp ({
res.json(await getMonitorUpdates());
}));

app.post('/api/v0/monitor', ipAuthMiddleware(exitnodeIPs), function(req, res) {
let ip = util.getRequestIP(req);
const key = `alive-${ip}`;
let handleErr = function(err) {
if (err) {
return res.status(502).json({ error: 'Could not set key, because of [' + err + '].' });
}
return res.json({ message: 'Set attached values', result: processed });
};
const processed = util.processUpdate(req);
if (processed.error) {
return res.status(400).json(processed);
} else {
mjs.set(key, JSON.stringify(processed), {expires: 120}, handleErr);
}
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yessssssssssss :-)

app.get('/api/v0/nodes', asyncMiddleware(async function(req, res) {
let data = await getRoutingTableUpdates();
res.json(data);
Expand Down
34 changes: 0 additions & 34 deletions tools/simulate-activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ if (require.main === module) {

async function main() {
await Promise.all([
simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 21}, exitnodeIPs[0]),
simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 29}, exitnodeIPs[1]),
simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|129.0.0.0/26,129.0.0.1|127.0.0.0/26,127.0.0.1|128.0.0.0/26,128.0.0.1|127.0.0.10/26,127.0.0.1|', exitnodeIPs[0]),
simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|125.0.0.0/26,124.0.0.1|121.0.0.0/26,121.0.0.1|123.0.0.0/26,123.0.0.1', exitnodeIPs[1]),
simulateRoutingTableRequest('', exitnodeIPs[2]) // simulate an empty POST
Expand All @@ -27,8 +25,6 @@ async function main() {
setTimeout(async () => {
// omit one routing table request to simulate a delay between posts
await Promise.all([
simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 21}, exitnodeIPs[0]),
simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 29}, exitnodeIPs[1]),
simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|129.0.0.0/26,129.0.0.1|127.0.0.0/26,127.0.0.1|128.0.0.0/26,128.0.0.1|127.0.0.10/26,127.0.0.1|', exitnodeIPs[0]),
simulateRoutingTableRequest('', exitnodeIPs[2]) // simulate an empty POST
])
Expand All @@ -37,36 +33,6 @@ async function main() {
})
}

async function simulateMonitorRequest(data, exitnodeIP) {
const response = await monitorRequest(data, exitnodeIP)
switch (response.statusCode) {
case 200:
console.info(`Successful /monitor request`)
break;
default:
throw new Error(`Unexpected statusCode from simulated monitor request: ${response.statusCode}`)
}
}

async function monitorRequest(data, exitnodeIP) {
return new Promise((resolve, reject) => {
var options = {
url: `http://localhost:${process.env.PORT}/api/v0/monitor`,
// the server authenticates the request by looking at
// its source ip or x-forwarded-for header
headers: {
'x-forwarded-for': exitnodeIP,
'content-type': 'application/json'
},
body: (typeof data === 'object') ? JSON.stringify(data) : data
};
request.post(options, (error, response) => {
if (error) return reject(error)
return resolve(response)
})
})
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yessssssssssss.

async function simulateRoutingTableRequest(body, exitnodeIP) {
const response = await routingTableRequest(body, exitnodeIP)
switch (response.statusCode) {
Expand Down