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

action[R]
agent[R]
command[R]
format[R]
request[R]
stderr[R]
stdout[R]

Public Class Methods

new(command, request, format=:json) click to toggle source
   # 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

canrun?(command) click to toggle source
    # File lib/mcollective/rpc/actionrunner.rb
117 def canrun?(command)
118   File.executable?(command)
119 end
load_json_results(file) click to toggle source
   # 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
load_results(file) click to toggle source
   # 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
path_to_command(command) click to toggle source
    # 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
run() click to toggle source
   # 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
save_json_request(req) click to toggle source
    # File lib/mcollective/rpc/actionrunner.rb
113 def save_json_request(req)
114   req.to_json
115 end
saverequest(req) click to toggle source
    # 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
shell(command, infile, outfile) click to toggle source
   # 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
tempfile(prefix) click to toggle source
    # File lib/mcollective/rpc/actionrunner.rb
125 def tempfile(prefix)
126   Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
127 end
to_s() click to toggle source
    # File lib/mcollective/rpc/actionrunner.rb
121 def to_s
122   "%s#%s command: %s" % [ agent, action, command ]
123 end