class MCollective::RPC::ActionRunner
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
Attributes
Public Class Methods
# File lib/mcollective/rpc/actionrunner.rb 26 def initialize(command, request, format=:json) 27 @agent = request.agent 28 @action = request.action 29 @format = format 30 @request = request 31 @command = path_to_command(command) 32 @stdout = "" 33 @stderr = "" 34 end
Public Instance Methods
# File lib/mcollective/rpc/actionrunner.rb 117 def canrun?(command) 118 File.executable?(command) 119 end
# File lib/mcollective/rpc/actionrunner.rb 91 def load_json_results(file) 92 return {} unless File.readable?(file) 93 94 JSON.load(File.read(file)) || {} 95 rescue JSON::ParserError 96 {} 97 end
# File lib/mcollective/rpc/actionrunner.rb 73 def load_results(file) 74 Log.debug("Attempting to load results in #{format} format from #{file}") 75 76 data = {} 77 78 if respond_to?("load_#{format}_results") 79 tempdata = send("load_#{format}_results", file) 80 81 tempdata.each_pair do |k,v| 82 data[k.to_sym] = v 83 end 84 end 85 86 data 87 rescue Exception => e 88 {} 89 end
# File lib/mcollective/rpc/actionrunner.rb 129 def path_to_command(command) 130 if Util.absolute_path?(command) 131 return command 132 end 133 134 Config.instance.libdir.each do |libdir| 135 command_file_old = File.join(libdir, "agent", agent, command) 136 command_file_new = File.join(libdir, "mcollective", "agent", agent, command) 137 command_file_old_exists = File.exists?(command_file_old) 138 command_file_new_exists = File.exists?(command_file_new) 139 140 if command_file_new_exists && command_file_old_exists 141 Log.debug("Found 'implemented_by' scripts found in two locations #{command_file_old} and #{command_file_new}") 142 Log.debug("Running script: #{command_file_new}") 143 return command_file_new 144 elsif command_file_old_exists 145 Log.debug("Running script: #{command_file_old}") 146 return command_file_old 147 elsif command_file_new_exists 148 Log.debug("Running script: #{command_file_new}") 149 return command_file_new 150 end 151 end 152 153 Log.warn("No script found for: #{command}") 154 command 155 end
# File lib/mcollective/rpc/actionrunner.rb 36 def run 37 unless canrun?(command) 38 Log.warn("Cannot run #{to_s}") 39 raise RPCAborted, "Cannot execute #{to_s}" 40 end 41 42 Log.debug("Running #{to_s}") 43 44 request_file = saverequest(request) 45 reply_file = tempfile("reply") 46 reply_file.close 47 48 runner = shell(command, request_file.path, reply_file.path) 49 50 runner.runcommand 51 52 Log.debug("#{command} exited with #{runner.status.exitstatus}") 53 54 stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty? 55 stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty? 56 57 {:exitstatus => runner.status.exitstatus, 58 :stdout => runner.stdout, 59 :stderr => runner.stderr, 60 :data => load_results(reply_file.path)} 61 ensure 62 request_file.close! if request_file.respond_to?("close!") 63 reply_file.close! if reply_file.respond_to?("close") 64 end
# File lib/mcollective/rpc/actionrunner.rb 113 def save_json_request(req) 114 req.to_json 115 end
# File lib/mcollective/rpc/actionrunner.rb 99 def saverequest(req) 100 Log.debug("Attempting to save request in #{format} format") 101 102 if respond_to?("save_#{format}_request") 103 data = send("save_#{format}_request", req) 104 105 request_file = tempfile("request") 106 request_file.puts data 107 request_file.close 108 end 109 110 request_file 111 end
# File lib/mcollective/rpc/actionrunner.rb 66 def shell(command, infile, outfile) 67 env = {"MCOLLECTIVE_REQUEST_FILE" => infile, 68 "MCOLLECTIVE_REPLY_FILE" => outfile} 69 70 Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env) 71 end
# File lib/mcollective/rpc/actionrunner.rb 125 def tempfile(prefix) 126 Tempfile.new("mcollective_#{prefix}", Dir.tmpdir) 127 end
# File lib/mcollective/rpc/actionrunner.rb 121 def to_s 122 "%s#%s command: %s" % [ agent, action, command ] 123 end