1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-03 09:49:17 +02:00
OpenSTF/lib/cli/local/index.js

408 lines
12 KiB
JavaScript

module.exports.command = 'local [serial..]'
module.exports.describe = 'Start a complete local development environment.'
module.exports.builder = function(yargs) {
var os = require('os')
return yargs
.env('STF_LOCAL')
.strict()
.option('adb-host', {
describe: 'The ADB server host.'
, type: 'string'
, default: '127.0.0.1'
})
.option('adb-port', {
describe: 'The ADB server port.'
, type: 'number'
, default: 5037
})
.option('allow-remote', {
alias: 'R'
, describe: 'Whether to allow remote devices in STF. Highly ' +
'unrecommended due to almost unbelievable slowness on the ADB side ' +
'and duplicate device issues when used locally while having a ' +
'cable connected at the same time.'
, type: 'boolean'
})
.option('api-port', {
describe: 'The port the api unit should run at.'
, type: 'number'
, default: 7106
})
.option('app-port', {
describe: 'The port the app unit should run at.'
, type: 'number'
, default: 7105
})
.option('auth-options', {
describe: 'JSON array of options to pass to the auth unit.'
, type: 'string'
, default: '[]'
})
.option('auth-port', {
describe: 'The port the auth unit should run at.'
, type: 'number'
, default: 7120
})
.option('auth-secret', {
describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' +
'knows this token can freely enter the system if they want, so keep ' +
'it safe.'
, type: 'string'
, default: 'kute kittykat'
})
.option('auth-type', {
describe: 'The type of auth unit to start.'
, type: 'string'
, choices: ['mock', 'ldap', 'oauth2', 'saml2', 'openid']
, default: 'mock'
})
.option('auth-url', {
alias: 'a'
, describe: 'URL to the auth unit.'
, type: 'string'
})
.option('bind-app-dealer', {
describe: 'The address to bind the app-side ZeroMQ DEALER endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7112'
})
.option('bind-app-pub', {
describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7111'
})
.option('bind-app-pull', {
describe: 'The address to bind the app-side ZeroMQ PULL endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7113'
})
.option('bind-dev-dealer', {
describe: 'The address to bind the device-side ZeroMQ DEALER endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7115'
})
.option('bind-dev-pub', {
describe: 'The address to bind the device-side ZeroMQ PUB endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7114'
})
.option('bind-dev-pull', {
describe: 'The address to bind the device-side ZeroMQ PULL endpoint to.'
, type: 'string'
, default: 'tcp://127.0.0.1:7116'
})
.option('cleanup', {
describe: 'Attempt to reset the device between uses by uninstalling' +
'apps, resetting accounts and clearing caches. Does not do a perfect ' +
'job currently. Negate with --no-cleanup.'
, type: 'boolean'
, default: true
})
.option('group-timeout', {
alias: 't'
, describe: 'Timeout in seconds for automatic release of inactive devices.'
, type: 'number'
, default: 900
})
.option('lock-rotation', {
describe: 'Whether to lock rotation when devices are being used. ' +
'Otherwise changing device orientation may not always work due to ' +
'sensitive sensors quickly or immediately reverting it back to the ' +
'physical orientation.'
, type: 'boolean'
})
.option('mute-master', {
describe: 'Whether to mute master volume when devices are being used.'
, type: 'boolean'
})
.option('port', {
alias: ['p', 'poorxy-port']
, describe: 'The port STF should run at.'
, type: 'number'
, default: 7100
})
.option('provider', {
describe: 'An easily identifiable name for the UI and/or log output.'
, type: 'string'
, default: os.hostname()
})
.option('provider-max-port', {
describe: 'Highest port number for device workers to use.'
, type: 'number'
, default: 7700
})
.option('provider-min-port', {
describe: 'Lowest port number for device workers to use.'
, type: 'number'
, default: 7400
})
.option('public-ip', {
describe: 'The IP or hostname to use in URLs.'
, type: 'string'
, default: 'localhost'
})
.option('serial', {
describe: 'Only use devices with these serial numbers.'
, type: 'array'
})
.option('storage-options', {
describe: 'JSON array of options to pass to the storage unit.'
, type: 'string'
, default: '[]'
})
.option('storage-plugin-apk-port', {
describe: 'The port the storage-plugin-apk unit should run at.'
, type: 'number'
, default: 7104
})
.option('storage-plugin-image-port', {
describe: 'The port the storage-plugin-image unit should run at.'
, type: 'number'
, default: 7103
})
.option('storage-port', {
describe: 'The port the storage unit should run at.'
, type: 'number'
, default: 7102
})
.option('storage-type', {
describe: 'The type of storage unit to start.'
, type: 'string'
, choices: ['temp', 's3']
, default: 'temp'
})
.option('user-profile-url', {
describe: 'URL to external user profile page'
, type: 'string'
})
.option('vnc-initial-size', {
describe: 'The initial size to use for the experimental VNC server.'
, type: 'string'
, default: '600x800'
, coerce: function(val) {
return val.split('x').map(Number)
}
})
.option('websocket-port', {
describe: 'The port the websocket unit should run at.'
, type: 'number'
, default: 7110
})
.epilog('Each option can be be overwritten with an environment variable ' +
'by converting the option to uppercase, replacing dashes with ' +
'underscores and prefixing it with `STF_LOCAL_` (e.g. ' +
'`STF_LOCAL_ALLOW_REMOTE`).')
}
module.exports.handler = function(argv) {
var util = require('util')
var path = require('path')
var Promise = require('bluebird')
var logger = require('../../util/logger')
var log = logger.createLogger('cli:local')
var procutil = require('../../util/procutil')
// Each forked process waits for signals to stop, and so we run over the
// default limit of 10. So, it's not a leak, but a refactor wouldn't hurt.
process.setMaxListeners(20)
function run() {
var procs = [
// app triproxy
procutil.fork(path.resolve(__dirname, '..'), [
'triproxy', 'app001'
, '--bind-pub', argv.bindAppPub
, '--bind-dealer', argv.bindAppDealer
, '--bind-pull', argv.bindAppPull
])
// device triproxy
, procutil.fork(path.resolve(__dirname, '..'), [
'triproxy', 'dev001'
, '--bind-pub', argv.bindDevPub
, '--bind-dealer', argv.bindDevDealer
, '--bind-pull', argv.bindDevPull
])
// processor one
, procutil.fork(path.resolve(__dirname, '..'), [
'processor', 'proc001'
, '--connect-app-dealer', argv.bindAppDealer
, '--connect-dev-dealer', argv.bindDevDealer
])
// processor two
, procutil.fork(path.resolve(__dirname, '..'), [
'processor', 'proc002'
, '--connect-app-dealer', argv.bindAppDealer
, '--connect-dev-dealer', argv.bindDevDealer
])
// reaper one
, procutil.fork(path.resolve(__dirname, '..'), [
'reaper', 'reaper001'
, '--connect-push', argv.bindDevPull
, '--connect-sub', argv.bindAppPub
])
// provider
, procutil.fork(path.resolve(__dirname, '..'), [
'provider'
, '--name', argv.provider
, '--min-port', argv.providerMinPort
, '--max-port', argv.providerMaxPort
, '--connect-sub', argv.bindDevPub
, '--connect-push', argv.bindDevPull
, '--group-timeout', argv.groupTimeout
, '--public-ip', argv.publicIp
, '--storage-url'
, util.format('http://localhost:%d/', argv.port)
, '--adb-host', argv.adbHost
, '--adb-port', argv.adbPort
, '--vnc-initial-size', argv.vncInitialSize.join('x')
]
.concat(argv.allowRemote ? ['--allow-remote'] : [])
.concat(argv.muteMaster ? ['--mute-master'] : [])
.concat(argv.lockRotation ? ['--lock-rotation'] : [])
.concat(!argv.cleanup ? ['--no-cleanup'] : [])
.concat(argv.serial))
// auth
, procutil.fork(path.resolve(__dirname, '..'), [
util.format('auth-%s', argv.authType)
, '--port', argv.authPort
, '--secret', argv.authSecret
, '--app-url', util.format(
'http://%s:%d/'
, argv.publicIp
, argv.port
)
].concat(JSON.parse(argv.authOptions)))
// app
, procutil.fork(path.resolve(__dirname, '..'), [
'app'
, '--port', argv.appPort
, '--secret', argv.authSecret
, '--auth-url', argv.authUrl || util.format(
'http://%s:%d/auth/%s/'
, argv.publicIp
, argv.port
, {
oauth2: 'oauth'
, saml2: 'saml'
}[argv.authType] || argv.authType
)
, '--websocket-url', util.format(
'http://%s:%d/'
, argv.publicIp
, argv.websocketPort
)
].concat((function() {
var extra = []
if (argv.userProfileUrl) {
extra.push('--user-profile-url', argv.userProfileUrl)
}
return extra
})()))
// api
, procutil.fork(path.resolve(__dirname, '..'), [
'api'
, '--port', argv.apiPort
, '--secret', argv.authSecret
, '--connect-push', argv.bindAppPull
, '--connect-sub', argv.bindAppPub
])
// websocket
, procutil.fork(path.resolve(__dirname, '..'), [
'websocket'
, '--port', argv.websocketPort
, '--secret', argv.authSecret
, '--storage-url'
, util.format('http://localhost:%d/', argv.port)
, '--connect-sub', argv.bindAppPub
, '--connect-push', argv.bindAppPull
])
// storage
, procutil.fork(path.resolve(__dirname, '..'), [
util.format('storage-%s', argv.storageType)
, '--port', argv.storagePort
].concat(JSON.parse(argv.storageOptions)))
// image processor
, procutil.fork(path.resolve(__dirname, '..'), [
'storage-plugin-image'
, '--port', argv.storagePluginImagePort
, '--storage-url'
, util.format('http://localhost:%d/', argv.port)
])
// apk processor
, procutil.fork(path.resolve(__dirname, '..'), [
'storage-plugin-apk'
, '--port', argv.storagePluginApkPort
, '--storage-url'
, util.format('http://localhost:%d/', argv.port)
])
// poorxy
, procutil.fork(path.resolve(__dirname, '..'), [
'poorxy'
, '--port', argv.port
, '--app-url'
, util.format('http://localhost:%d/', argv.appPort)
, '--auth-url'
, util.format('http://localhost:%d/', argv.authPort)
, '--api-url'
, util.format('http://localhost:%d/', argv.apiPort)
, '--websocket-url'
, util.format('http://localhost:%d/', argv.websocketPort)
, '--storage-url'
, util.format('http://localhost:%d/', argv.storagePort)
, '--storage-plugin-image-url'
, util.format('http://localhost:%d/', argv.storagePluginImagePort)
, '--storage-plugin-apk-url'
, util.format('http://localhost:%d/', argv.storagePluginApkPort)
])
]
function shutdown() {
log.info('Shutting down all child processes')
procs.forEach(function(proc) {
proc.cancel()
})
return Promise.settle(procs)
}
process.on('SIGINT', function() {
log.info('Received SIGINT, waiting for processes to terminate')
})
process.on('SIGTERM', function() {
log.info('Received SIGTERM, waiting for processes to terminate')
})
return Promise.all(procs)
.then(function() {
process.exit(0)
})
.catch(function(err) {
log.fatal('Child process had an error', err.stack)
return shutdown()
.then(function() {
process.exit(1)
})
})
}
return procutil.fork(__filename, ['migrate'])
.done(run)
}