Skip to content

Commit 22afb91

Browse files
committed
factor out pinging logic into a class and make the cloud installer use it too
1 parent 7e7ed30 commit 22afb91

File tree

4 files changed

+72
-44
lines changed

4 files changed

+72
-44
lines changed

src/cloud/digitalocean/provisioner.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference path='../../../../third_party/typings/browser.d.ts' />
22

3-
import promises = require('../../promises/promises');
3+
import Pinger = require('../../net/pinger');
44

55
declare const freedom: freedom.FreedomInModuleEnv;
66

@@ -110,31 +110,11 @@ class Provisioner {
110110
}
111111
}
112112

113-
// Spin until the server is truly up.
114-
// Give it one minute before declaring bankruptcy.
115-
console.log('waiting for activity on port 22');
116-
return promises.retry(() => {
117-
const socket = freedom['core.tcpsocket']();
118-
119-
const destructor = () => {
120-
try {
121-
freedom['core.tcpsocket'].close(socket);
122-
} catch (e) {
123-
console.warn('error destroying socket: ' + e.message);
124-
}
125-
};
126-
127-
// TODO: Worth thinking about timeouts here but because this times
128-
// out almost immediately if nothing is listening on the port,
129-
// it works well for our purposes.
130-
return socket.connect(this.state_.network['ipv4'], 22).then((unused: any) => {
131-
destructor();
132-
return this.state_;
133-
}, (e: Error) => {
134-
destructor();
135-
throw e;
136-
});
137-
}, 60, 1000);
113+
// It usually takes several seconds after the API reports success for
114+
// SSH on a new droplet to become responsive.
115+
return new Pinger(this.state_.network['ipv4'], 22, 60).ping().then(() => {
116+
return this.state_;
117+
});
138118
});
139119
}
140120

src/cloud/install/installer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require('../social/monkey/process');
55
import arraybuffers = require('../../arraybuffers/arraybuffers');
66
import linefeeder = require('../../net/linefeeder');
77
import logging = require('../../logging/logging');
8+
import Pinger = require('../../net/pinger');
89
import queue = require('../../handler/queue');
910

1011
// https://github.com/borisyankov/DefinitelyTyped/blob/master/ssh2/ssh2-tests.ts
@@ -143,6 +144,12 @@ class CloudInstaller {
143144
message: 'connection close without invitation URL'
144145
});
145146
}).connect(connectConfig);
147+
}).then((invite: any) => {
148+
// It can take several seconds before the SSH server running
149+
// in the new Docker container becomes active.
150+
return new Pinger(host, 5000).ping().then(() => {
151+
return invite;
152+
});
146153
});
147154
}
148155
}

src/cloud/social/provider.ts

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import arraybuffers = require('../../arraybuffers/arraybuffers');
66
import crypto = require('crypto');
77
import linefeeder = require('../../net/linefeeder');
88
import logging = require('../../logging/logging');
9-
import promises = require('../../promises/promises');
109
import queue = require('../../handler/queue');
1110

1211
// https://github.com/borisyankov/DefinitelyTyped/blob/master/ssh2/ssh2-tests.ts
@@ -209,24 +208,15 @@ export class CloudSocialProvider {
209208
});
210209
}
211210

212-
let numAttempts = 0;
213-
const connect = () => {
214-
log.debug('connection attempt %1...', (++numAttempts));
215-
const connection = new Connection(invite, (message: Object) => {
216-
this.dispatchEvent_('onMessage', {
217-
from: makeClientState(invite.host),
218-
// SIGNAL_FROM_SERVER_PEER,
219-
message: JSON.stringify(makeVersionedPeerMessage(3002, message))
220-
});
221-
});
222-
return connection.connect().then(() => {
223-
return connection;
211+
const connection = new Connection(invite, (message: Object) => {
212+
this.dispatchEvent_('onMessage', {
213+
from: makeClientState(invite.host),
214+
// SIGNAL_FROM_SERVER_PEER,
215+
message: JSON.stringify(makeVersionedPeerMessage(3002, message))
224216
});
225-
};
226-
227-
this.clients_[invite.host] = promises.retryWithExponentialBackoff(connect,
228-
MAX_CONNECTION_INTERVAL_MS, INITIAL_CONNECTION_INTERVAL_MS).then(
229-
(connection:Connection) => {
217+
});
218+
219+
this.clients_[invite.host] = connection.connect().then(() => {
230220
log.info('connected to zork on %1', invite.host);
231221

232222
// Fetch the banner, if available, then emit an instance message.

src/net/pinger.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// <reference path='../../../third_party/typings/browser.d.ts' />
2+
3+
import logging = require('../logging/logging');
4+
import promises = require('../promises/promises');
5+
6+
declare const freedom: freedom.FreedomInModuleEnv;
7+
8+
var log: logging.Log = new logging.Log('pinger');
9+
10+
const DEFAULT_TIMEOUT_SECS = 60;
11+
const DEFAULT_INTERVAL_MS = 1000;
12+
13+
// "Pings" - in an nmap sense - a port until a TCP connection can be established.
14+
class Pinger {
15+
constructor(
16+
private host_: string,
17+
private port_: number,
18+
private timeout_= DEFAULT_TIMEOUT_SECS) { }
19+
20+
// Resolves once a connection has been established, rejecting if
21+
// no connection can be made within the timeout.
22+
public ping = (): Promise<void> => {
23+
log.debug('pinging %1:%2...', this.host_, this.port_);
24+
25+
return promises.retry(() => {
26+
const socket = freedom['core.tcpsocket']();
27+
28+
const destructor = () => {
29+
try {
30+
freedom['core.tcpsocket'].close(socket);
31+
} catch (e) {
32+
console.warn('error destroying socket: ' + e.message);
33+
}
34+
};
35+
36+
// TODO: Worth thinking about timeouts here but because this times
37+
// out almost immediately if nothing is listening on the port,
38+
// it works well for our purposes.
39+
return socket.connect(this.host_, this.port_).then((unused: any) => {
40+
log.debug('connected to %1:%2', this.host_, this.port_);
41+
destructor();
42+
}, (e: Error) => {
43+
log.debug('connection failed to %1:%2', this.host_, this.port_);
44+
destructor();
45+
throw e;
46+
});
47+
}, this.timeout_, DEFAULT_INTERVAL_MS);
48+
}
49+
}
50+
51+
export = Pinger;

0 commit comments

Comments
 (0)