1
|
|
|
'use strict'; |
2
|
|
|
|
3
|
|
|
let |
4
|
|
|
execFile = require('child_process').execFile, |
5
|
|
|
Bluebird = require('bluebird'), |
6
|
|
|
retry = require('p-retry'), |
7
|
|
|
Log = require('./../../../core/log').Log, |
8
|
|
|
Request = require('./../../../core/request').Request, |
9
|
|
|
ProcessBase = require('./../../../core/process').Process |
10
|
|
|
|
11
|
|
|
let log = new Log('Platforms.Core.Tunnel.Process') |
12
|
|
|
|
13
|
|
|
class Process extends ProcessBase { |
14
|
|
|
|
15
|
|
|
constructor(pid, tunnelId) { |
16
|
|
|
super(pid) |
17
|
|
|
this.tunnelId = tunnelId |
18
|
|
|
} |
19
|
|
|
|
20
|
|
|
create(command, args, callbacks, argsParser, stdoutFnCreator, exitFn) { |
21
|
|
|
argsParser(args) |
22
|
|
|
callbacks = callbacks || { } |
23
|
|
|
if(!callbacks.onstderr) { |
24
|
|
|
callbacks.onstderr = (err) => { |
25
|
|
|
log.warn('unexpected stderr', err, this) |
26
|
|
|
} |
27
|
|
|
} |
28
|
|
|
return new Bluebird((resolve, reject) => { |
29
|
|
|
this.proc = execFile(command, args) |
30
|
|
|
this.pid = this.proc.pid |
31
|
|
|
log.debug('execFile %s %s, created %d', command, args.join(' '), this.pid) |
32
|
|
|
this.proc.stdout.on('data', stdoutFnCreator(this, command, args, resolve, reject)) |
33
|
|
|
this.proc.stderr.on('data', callbacks.onstderr) |
34
|
|
|
this.proc.on('error', reject) |
35
|
|
|
this.proc.on('exit', code => { |
36
|
|
|
exitFn(this, command, args, code, reject) |
37
|
|
|
}) |
38
|
|
|
}) |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
stop(isStopped, getStatus, auth) { |
42
|
|
|
this.stopping = true |
43
|
|
|
return apiReq(this.endpoint, 'DELETE', auth) |
44
|
|
|
.then(() => { |
45
|
|
|
return waitForServerExit(this, isStopped, getStatus, auth) |
46
|
|
|
}) |
47
|
|
|
.then(() => { |
48
|
|
|
return ProcessBase.prototype.stop.call(this) |
49
|
|
|
}) |
50
|
|
|
.then(() => { |
51
|
|
|
return waitForExit(this) |
52
|
|
|
}) |
53
|
|
|
.catch(err => { |
54
|
|
|
if(err.message.match(/already stopped/)) { |
55
|
|
|
return Promise.resolve(true) |
56
|
|
|
} |
57
|
|
|
throw err |
58
|
|
|
}) |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
function waitForServerExit(proc, isStopped, getStatus, auth) { |
64
|
|
|
let max = 60, factor = 1, minTimeout = 2000 |
65
|
|
|
const check = () => { |
66
|
|
|
return apiReq(proc.endpoint, 'GET', auth) |
67
|
|
|
.then(response => { |
68
|
|
|
if(isStopped(response)) { |
69
|
|
|
log.debug('server tunnel instance (serverId %s) corresponding to tunnel with pid %d is stopped', proc.serverId, proc.pid) |
70
|
|
|
return true |
71
|
|
|
} |
72
|
|
|
log.debug('serverId %s not terminated yet on server, status is %s', proc.pid, proc.serverId, getStatus(response)) |
73
|
|
|
throw new Error('Platforms.Core.Tunnel.Process: not terminated on server side yet') |
74
|
|
|
}) |
75
|
|
|
} |
76
|
|
|
return retry(check, { retries: max, minTimeout: minTimeout, factor: factor }) |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
function waitForExit(proc) { |
80
|
|
|
let max = 60, factor = 1, minTimeout = 2000 |
81
|
|
|
const check = () => { |
82
|
|
|
if('stopped' === proc.status()) { |
83
|
|
|
log.debug('killed') |
84
|
|
|
delete proc.stopping |
85
|
|
|
return true |
86
|
|
|
} |
87
|
|
|
log.debug('still alive...') |
88
|
|
|
throw new Error('waiting for process to die') |
89
|
|
|
} |
90
|
|
|
return retry(check, { retries: max, minTimeout: minTimeout, factor: factor }) |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
function apiReq(url, method, auth) { |
94
|
|
|
let options = { |
95
|
|
|
json: true, |
96
|
|
|
auth: auth |
97
|
|
|
} |
98
|
|
|
let req = new Request() |
99
|
|
|
return req.request(url, method, options) |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
exports.Process = Process |
103
|
|
|
|