UNPKG

masson

Version:

Module execution engine for cluster deployments.

156 lines (147 loc) 5.56 kB
crypto = require 'crypto' util = require 'util' pad = require 'pad' each = require 'each' {EventEmitter} = require 'events' {flatten, merge} = require './misc' context = require './context' {Tree} = require './tree' ### The execution is done in 2 passes. On the first pass, a context object is build for each server. A context is the same object inject to a callback action as first argument. In a context, other server contexts are available through the `hosts` object where keys are the server name. A context object is enriched with the "actions" and "modules" properties which are respectively a list of "actions" and a list of modules. On the second pass, the action are executed. ### Run = (config, params) -> EventEmitter.call @ @setMaxListeners 100 @config = config @params = params @tree = new Tree # @tree = new Tree setImmediate => # Work on each server contexts = {} shared = {} each(config.servers) .parallel(true) .on 'item', (server, next) => ctx = contexts[server.host] = context (merge {}, config, server), params.command ctx.hosts = contexts ctx.shared = shared @actions server.host, 'install', {}, (err, actions) => return next err if err ctx.actions = actions or [] @modules server.host, 'install', {}, (err, modules) => return next err if err ctx.modules = modules or [] next() .on 'error', (err) => @emit 'error', err .on 'end', => process.on 'uncaughtException', (err) => for host, ctx of contexts ctx.emit 'error', err if ctx.listeners('error').length @emit 'error', err each(config.servers) .parallel(true) .on 'item', (server, next) => # Filter by hosts return next() if params.hosts? and params.hosts.indexOf(server.host) is -1 ctx = contexts[server.host] ctx.run = @ @emit 'context', ctx @actions server.host, params.command, params, (err, actions) => # return next new Error "Invalid run list: #{@params.command}" unless actions? return next() unless actions? actionRun = each(actions) .on 'item', (action, next) => return next() if action.skip # Action ctx.action = action retry = action.retry or 2 attempts = 0 emit_action = (status) => ctx.emit 'action', status emit_action ctx.STARTED done = (err, statusOrMsg) => clearTimeout timeout if timeout if err and (retry is true or ++attempts < retry) ctx.log "Get error #{err.message}, retry #{attempts} of #{retry}" return setTimeout(run, 1) if err then emit_action ctx.FAILED else emit_action statusOrMsg next err run = => # Synchronous action if action.callback.length is 1 merge action, action.callback.call ctx, ctx process.nextTick -> action.timeout = -1 done null, ctx.DISABLED # Asynchronous action else merge action, action.callback.call ctx, ctx, (err, statusOrMsg) => actionRun.end() if statusOrMsg is ctx.STOP done err, statusOrMsg # Prevent "Maximum call stack size exceeded" when # timeout, interval and setImmediate is used inside the callback # Timeout, default to 100s action.timeout ?= 100000 if action.timeout > 0 timeout = setTimeout -> done new Error 'TIMEOUT' , action.timeout try setImmediate run catch e then done e .on 'both', (err) => @emit 'server', ctx, if err then ctx.FAILED else ctx.OK if err then (ctx.emit 'error', err if ctx.listeners('error').length) else ctx.emit 'end' next err .on 'error', (err) => @emit 'error', err .on 'end', (err) => @emit 'end' @ util.inherits Run, EventEmitter ### Return all the actions for a given host and the current command or null if the host didnt register any run list for this command. ### Run::actions = (host, command, options, callback) -> # Get the server config for server in @config.servers config = server if server.host is host return callback new Error "Invalid host: #{host}" unless config # Check the run list run = config.run[command] return callback null unless run # return callback new Error "Invalid run list: #{@params.command}" unless run @tree.actions run, options, (err, actions) => return callback err if err callback null, actions ### Return all the modules for a given host and the current command or null if the host didnt register any run list for this command. ### Run::modules = (host, command, options, callback) -> # Get the server config for server in @config.servers config = server if server.host is host return callback new Error "Invalid host: #{host}" unless config # Check the run list run = config.run[command] return callback null unless run # return callback new Error "Invalid run list: #{@params.command}" unless run @tree.modules run, options, (err, modules) => return callback err if err callback null, modules module.exports = (config, params) -> new Run config, params