X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Frpc%2Factionrunner.rb;fp=lib%2Fmcollective%2Frpc%2Factionrunner.rb;h=f2a65d502c07c69a69c6906db4b5d5c016d3a70b;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/rpc/actionrunner.rb b/lib/mcollective/rpc/actionrunner.rb new file mode 100644 index 0000000..f2a65d5 --- /dev/null +++ b/lib/mcollective/rpc/actionrunner.rb @@ -0,0 +1,142 @@ +module MCollective + module RPC + # A helper used by RPC::Agent#implemented_by to delegate an action to + # an external script. At present only JSON based serialization is + # supported in future ones based on key=val pairs etc will be added + # + # It serializes the request object into an input file and creates an + # empty output file. It then calls the external command reading the + # output file at the end. + # + # any STDERR gets logged at error level and any STDOUT gets logged at + # info level. + # + # It will interpret the exit code from the application the same way + # RPC::Reply#fail! and #fail handles their codes creating a consistent + # interface, the message part of the fail message will come from STDERR + # + # Generally externals should just exit with code 1 on failure and print to + # STDERR, this is exactly what Perl die() does and translates perfectly + # to our model + # + # It uses the MCollective::Shell wrapper to call the external application + class ActionRunner + attr_reader :command, :agent, :action, :format, :stdout, :stderr, :request + + def initialize(command, request, format=:json) + @agent = request.agent + @action = request.action + @format = format + @request = request + @command = path_to_command(command) + @stdout = "" + @stderr = "" + end + + def run + unless canrun?(command) + Log.warn("Cannot run #{to_s}") + raise RPCAborted, "Cannot execute #{to_s}" + end + + Log.debug("Running #{to_s}") + + request_file = saverequest(request) + reply_file = tempfile("reply") + reply_file.close + + runner = shell(command, request_file.path, reply_file.path) + + runner.runcommand + + Log.debug("#{command} exited with #{runner.status.exitstatus}") + + stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty? + stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty? + + {:exitstatus => runner.status.exitstatus, + :stdout => runner.stdout, + :stderr => runner.stderr, + :data => load_results(reply_file.path)} + ensure + request_file.close! if request_file.respond_to?("close!") + reply_file.close! if reply_file.respond_to?("close") + end + + def shell(command, infile, outfile) + env = {"MCOLLECTIVE_REQUEST_FILE" => infile, + "MCOLLECTIVE_REPLY_FILE" => outfile} + + Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env) + end + + def load_results(file) + Log.debug("Attempting to load results in #{format} format from #{file}") + + data = {} + + if respond_to?("load_#{format}_results") + tempdata = send("load_#{format}_results", file) + + tempdata.each_pair do |k,v| + data[k.to_sym] = v + end + end + + data + rescue Exception => e + {} + end + + def load_json_results(file) + return {} unless File.readable?(file) + + JSON.load(File.read(file)) || {} + rescue JSON::ParserError + {} + end + + def saverequest(req) + Log.debug("Attempting to save request in #{format} format") + + if respond_to?("save_#{format}_request") + data = send("save_#{format}_request", req) + + request_file = tempfile("request") + request_file.puts data + request_file.close + end + + request_file + end + + def save_json_request(req) + req.to_json + end + + def canrun?(command) + File.executable?(command) + end + + def to_s + "%s#%s command: %s" % [ agent, action, command ] + end + + def tempfile(prefix) + Tempfile.new("mcollective_#{prefix}", Dir.tmpdir) + end + + def path_to_command(command) + unless command[0,1] == File::SEPARATOR + Config.instance.libdir.each do |libdir| + command_file = File.join(libdir, "agent", agent, command) + + return command_file if File.exist?(command_file) + end + end + + return command + end + end + end +end