This repository has been archived by the owner on Nov 30, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserver.js
145 lines (130 loc) · 5.54 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
var dns = require('dns');
var ip = require('ip');
var timer = require('timers');
var express = require('express');
var generatePassword = require('password-generator');
var request = require('request');
var isDocker = require('is-docker');
var app = express();
var data = {};
// Counter deletions since last global rehash
var deletecounter = 0;
var suffix = process.env.INSP_SUFFIX || ".example.com"
var leafsize = process.env.INSP_CLUSTERSIZE || 3;
var servicename = process.env.INSP_SERVICENAME || null;
// Generate configs
app.get('/conf/:id/:fingerprint?', function (req, res) {
var id = req.params.id;
var fingerprint = req.params.fingerprint || null;
// Make sure there is a dataset for this node.
if (!data[id]) {
data[id] = {};
data[id].id = id;
// Maybe our discovery is hidden behind a reverse proxy
data[id].ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
data[id].password = generatePassword(64, false);
if (fingerprint)
data[id].fingerprint = fingerprint;
// Notify all nodes to rehash confgs to make the node known in the existing cluster.
rehashAll();
// maybe the nodes ip changed. Make sure we update it.
} else if ((data[id].ip !== req.headers['x-forwarded-for']) && (data[id].ip !== req.connection.remoteAddress)) {
data[id].ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
rehashAll();
} else if (data[id].fingerprint && fingerprint && data[id].fingerprint !== fingerprint) {
data[id].fingerprint = fingerprint;
rehashAll();
}
var confresponse = "";
// Generating config
for(var key in data) {
remote = data[key];
// create a link tag for every server excluding ourself
if (remote.id != id) {
confresponse += '<link name="' + remote.id + suffix + '" ' +
'ipaddr="'+ remote.id +'" ' +
'port="7001" ' +
'allowmask="' + remote.ip + (ip.isV6Format(remote.ip) ? '/128" ' : '/32" ') +
'timeout="300" ' +
'ssl="gnutls" ' +
'statshidden="no" ' +
(remote.fingerprint ? ('fingerprint="' + remote.fingerprint + '" ') : '') +
'hidden="no" ' +
'sendpass="' + remote.password + '" ' +
'recvpass="' + data[id].password + '">\n';
}
}
// To let servers automatically join the cluster
confresponse += buildAutoconnect(data[id].id);
res.send(confresponse);
});
// Garbage collection
timer.setInterval(function() {
console.log("Running garbage collection");
for(var key in data) {
var collectGarbage = function (key) {
// if the node is not resolveable it's dead. We have to remove it from the cluster
dns.lookup(data[key].id, function(err) {
if (err) {
deletecounter++;
delete(data[key]);
console.log("Delete " + key + suffix);
// If we delete as many nodes as the size of one leaf we may loose an whole uplink. In order to prevent that we rehash all nodes and buod a new cluster structure.
if (deletecounter > leafsize - 1)
rehashAll();
}
});
};
collectGarbage(key);
}
}, 15 * 1000);
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
});
// Solve the bootstrap problem
if (servicename && isDocker())
dns.lookup("tasks." + servicename, {all: true}, function (err, addresses) {
if (err)
return;
addresses.forEach(function(result) {
var remote = {id: result.address};
rehash(remote);
})
});
function rehash(remote) {
request.get("http://" + remote.id + ":8080/rehash", function () {
console.log("Rehashing " + remote.id + suffix + "...");
});
}
function rehashAll() {
deletecounter = 0;
for(var key in data) {
rehash(data[key]);
}
}
function buildAutoconnect(node) {
var returnvalue = "";
var hubs = [];
// Get a array of all nodes
var dataset = Object.keys(data);
// Find the current node
var nodeindex = dataset.indexOf(node);
// Determine the beginning of the leaf
var leafindex = nodeindex - (nodeindex % leafsize);
// Determine the beginning of next uplink leaf
var uplinkindex = leafindex / leafsize - ((leafindex / leafsize) % leafsize);
// If uplinks exists create a single entry for all nodes of the next uplink
// This way we only try to connect one at the same time.
if (uplinkindex !== 0) {
for(var i = uplinkindex; i < (uplinkindex + leafsize) && i < dataset.length; i++)
hubs.push(dataset[i] + suffix);
if (hubs.length > 0)
// Randomize order to minimize the chance of multiple servers starting handshake with the same uplink node
returnvalue += '<autoconnect period="' + ((Math.random() * (2 * leafsize) + 10 | 0)) + '" server="' + hubs.sort(function(a, b){return 0.5 - Math.random()}).join(" ") + '">';
}
// Create one autoconnect tag for every node on our leaf to make sure noone gets lost.
for (var i = leafindex; i < (leafindex + leafsize) && i < dataset.length; i++)
if (i !== nodeindex)
returnvalue += '<autoconnect period="' + ((Math.random() * (5 * leafsize) + 30) | 0) + '" server="' + dataset[i] + suffix + '">';
return returnvalue;
}