1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-04 02:09:32 +02:00
OpenSTF/lib/roles/provider.js
2014-01-28 16:53:10 +09:00

186 lines
4.7 KiB
JavaScript

var path = require('path')
var adb = require('adbkit')
var Promise = require('bluebird')
var zmq = require('zmq')
var logger = require('../util/logger')
var wire = require('../wire')
var wireutil = require('../util/wireutil')(wire)
module.exports = function(options) {
var log = logger.createLogger('provider')
var client = Promise.promisifyAll(adb.createClient())
var workers = Object.create(null)
// Output
var push = zmq.socket('push')
options.endpoints.push.forEach(function(endpoint) {
log.info('Sending output to %s', endpoint)
push.connect(endpoint)
})
client.trackDevicesAsync()
.then(function(tracker) {
log.info('Tracking devices')
tracker.on('add', function(device) {
if (isWantedDevice(device)) {
log.info('Found device "%s" (%s)', device.id, device.type)
maybeConnect(device)
}
else {
log.info('Ignoring device "%s" (%s)', device.id, device.type)
}
})
tracker.on('change', function(device) {
if (isWantedDevice(device)) {
log.info('Device "%s" is now "%s"', device.id, device.type)
maybeConnect(device) || maybeDisconnect(device)
}
})
tracker.on('remove', function(device) {
if (isWantedDevice(device)) {
log.info('Lost device "%s" (%s)', device.id, device.type)
maybeDisconnect(device)
}
})
})
function pushDeviceStatus(device, type) {
push.send([wireutil.global,
wireutil.makeDeviceStatusMessage(device.id, type)])
}
function isWantedDevice(device) {
return options.filter ? options.filter(device) : true
}
function isConnectable(device) {
switch (device.type) {
case 'device':
case 'emulator':
return true
default:
return false
}
}
function isConnected(device) {
return workers[device.id]
}
function maybeConnect(device) {
if (isConnectable(device) && !isConnected(device)) {
pushDeviceStatus(device, device.type)
log.info('Spawning worker for device "%s"', device.id)
var proc = options.fork(device)
proc.on('error', function(err) {
log.error('Device worker "%s" had an error: %s',
device.id, err.message)
})
proc.on('exit', function(code, signal) {
var data = workers[device.id]
delete workers[device.id]
if (code === 0) {
log.info('Device worker "%s" stopped cleanly', device.id)
}
else {
log.error('Device worker "%s" had a dirty exit (code %d)',
device.id, code)
if (Date.now() - data.started < 10000) {
log.error('Device worker "%s" failed within 10 seconds of startup,' +
' will not attempt to restart', device.id)
}
else {
log.info('Restarting worker of "%s"', device.id)
maybeConnect(device)
}
}
})
workers[device.id] = {
device: device
, proc: proc
, started: Date.now()
}
return true
}
return false
}
function maybeDisconnect(device) {
if (isConnected(device)) {
pushDeviceStatus(device, 'absent')
log.info('Releasing worker of %s', device.id)
gracefullyKillWorker(device.id)
return true
}
return false
}
function tryKillWorker(id) {
var deferred = Promise.defer(),
worker = workers[id]
function onExit() {
deferred.resolve()
}
worker.proc.once('exit', onExit)
worker.proc.kill('SIGTERM')
return deferred.promise.finally(function() {
worker.proc.removeListener('exit', onExit)
})
}
function forceKillWorker(id) {
log.warn('Force killing worker of device "%s"', id)
var deferred = Promise.defer()
, worker = workers[id]
function onExit() {
deferred.resolve()
}
worker.proc.once('exit', onExit)
worker.proc.kill('SIGKILL')
return deferred.promise.finally(function() {
worker.proc.removeListener('exit', onExit)
})
}
function gracefullyKillWorker(id) {
return tryKillWorker(id)
.timeout(10000)
.catch(function() {
log.error('Device worker "%s" did not stop in time', id)
return forceKillWorker(id)
.timeout(10000)
.then(deferred.resolve)
})
}
function gracefullyExit() {
log.info('Stopping all workers')
Promise.all(Object.keys(workers).map(gracefullyKillWorker))
.done(function() {
log.info('All cleaned up')
process.exit(0)
})
}
process.on('SIGINT', function(e) {
log.info('Received SIGINT')
gracefullyExit()
})
process.on('SIGTERM', function(e) {
log.info('Received SIGTERM')
gracefullyExit()
})
}