Parent

Included Modules

Class Index [+]

Quicksearch

MCollective::RPC::Agent

A wrapper around the traditional agent, it takes care of a lot of the tedious setup you would do for each agent allowing you to just create methods following a naming standard leaving the heavy lifting up to this clas.

See marionette-collective.org/simplerpc/agents.html

It only really makes sense to use this with a Simple RPC client on the other end, basic usage would be:

   module MCollective
     module Agent
       class Helloworld<RPC::Agent
         action "hello" do
           reply[:msg] = "Hello #{request[:name]}"
         end

         action "foo" do
           implemented_by "/some/script.sh"
         end
       end
     end
   end

If you wish to implement the logic for an action using an external script use the implemented_by method that will cause your script to be run with 2 arguments.

The first argument is a file containing JSON with the request and the 2nd argument is where the script should save its output as a JSON hash.

We also currently have the validation code in here, this will be moved to plugins soon.

Attributes

reply[RW]

(Not documented)

request[RW]

(Not documented)

agent_name[RW]

(Not documented)

logger[R]

(Not documented)

config[R]

(Not documented)

timeout[R]

(Not documented)

ddl[R]

(Not documented)

meta[R]

(Not documented)

Public Class Methods

actions() click to toggle source

Returns an array of actions this agent support

     # File lib/mcollective/rpc/agent.rb, line 157
157:       def self.actions
158:         public_instance_methods.sort.grep(/_action$/).map do |method|
159:           $1 if method =~ /(.+)_action$/
160:         end
161:       end
activate?() click to toggle source

By default RPC Agents support a toggle in the configuration that can enable and disable them based on the agent name

Example an agent called Foo can have:

plugin.foo.activate_agent = false

and this will prevent the agent from loading on this particular machine.

Agents can use the activate_when helper to override this for example:

activate_when do

   File.exist?("/usr/bin/puppet")

end

     # File lib/mcollective/rpc/agent.rb, line 142
142:       def self.activate?
143:         agent_name = self.to_s.split("::").last.downcase
144: 
145:         log_code(:PLMC37, "Starting default activation checks for the '%{agent}' agent", :debug, :agent => agent_name)
146: 
147:         should_activate = Util.str_to_bool(Config.instance.pluginconf.fetch("#{agent_name}.activate_agent", true))
148: 
149:         unless should_activate
150:           log_code(:PLMC38, "Found plugin configuration '%{agent}.activate_agent' with value '%{should_activate}'", :debug, :agent => agent_name, :should_activate => should_activate)
151:         end
152: 
153:         return should_activate
154:       end
new() click to toggle source

(Not documented)

    # File lib/mcollective/rpc/agent.rb, line 40
40:       def initialize
41:         @agent_name = self.class.to_s.split("::").last.downcase
42: 
43:         load_ddl
44: 
45:         @logger = Log.instance
46:         @config = Config.instance
47: 
48:         # if we have a global authorization provider enable it
49:         # plugins can still override it per plugin
50:         self.class.authorized_by(@config.rpcauthprovider) if @config.rpcauthorization
51: 
52:         startup_hook
53:       end

Private Class Methods

action(name, &block) click to toggle source

Creates a new action with the block passed and sets some defaults

action “status” do

   # logic here to restart service

end

     # File lib/mcollective/rpc/agent.rb, line 260
260:       def self.action(name, &block)
261:         raise "Need to pass a body for the action" unless block_given?
262: 
263:         self.module_eval { define_method("#{name}_action", &block) }
264:       end
activate_when(&block) click to toggle source

Creates the needed activate? class in a manner similar to the other helpers like action, authorized_by etc

activate_when do

   File.exist?("/usr/bin/puppet")

end

     # File lib/mcollective/rpc/agent.rb, line 249
249:       def self.activate_when(&block)
250:         (class << self; self; end).instance_eval do
251:           define_method("activate?", &block)
252:         end
253:       end
authorized_by(plugin) click to toggle source

Helper that creates a method on the class that will call your authorization plugin. If your plugin raises an exception that will abort the request

     # File lib/mcollective/rpc/agent.rb, line 268
268:       def self.authorized_by(plugin)
269:         plugin = plugin.to_s.capitalize
270: 
271:         # turns foo_bar into FooBar
272:         plugin = plugin.to_s.split("_").map {|v| v.capitalize}.join
273:         pluginname = "MCollective::Util::#{plugin}"
274: 
275:         PluginManager.loadclass(pluginname) unless MCollective::Util.constants.include?(plugin)
276: 
277:         class_eval("
278:                       def authorization_hook(request)
279:                    #{pluginname}.authorize(request)
280:                       end
281:                    ")
282:       end
metadata(data) click to toggle source

Registers meta data for the introspection hash

     # File lib/mcollective/rpc/agent.rb, line 237
237:       def self.metadata(data)
238:         agent = File.basename(caller.first).split(":").first
239: 
240:         log_code(:PLMC34, "setting meta data in agents have been deprecated, DDL files are now being used for this information. Please update the '%{agent}' agent", :warn,  :agent => agent)
241:       end

Public Instance Methods

handlemsg(msg, connection) click to toggle source

(Not documented)

     # File lib/mcollective/rpc/agent.rb, line 64
 64:       def handlemsg(msg, connection)
 65:         @request = RPC::Request.new(msg, @ddl)
 66:         @reply = RPC::Reply.new(@request.action, @ddl)
 67: 
 68:         begin
 69:           # Incoming requests need to be validated against the DDL thus reusing
 70:           # all the work users put into creating DDLs and creating a consistent
 71:           # quality of input validation everywhere with the a simple once off
 72:           # investment of writing a DDL
 73:           @request.validate!
 74: 
 75:           # Calls the authorization plugin if any is defined
 76:           # if this raises an exception we wil just skip processing this
 77:           # message
 78:           authorization_hook(@request) if respond_to?("authorization_hook")
 79: 
 80:           # Audits the request, currently continues processing the message
 81:           # we should make this a configurable so that an audit failure means
 82:           # a message wont be processed by this node depending on config
 83:           audit_request(@request, connection)
 84: 
 85:           before_processing_hook(msg, connection)
 86: 
 87:           if respond_to?("#{@request.action}_action")
 88:             send("#{@request.action}_action")
 89:           else
 90:             log_code(:PLMC36, "Unknown action '%{action}' for agent '%{agent}'", :warn, :action => @request.action, :agent => @request.agent)
 91:             raise UnknownRPCAction, "Unknown action '#{@request.action}' for agent '#{@request.agent}'"
 92:           end
 93:         rescue RPCAborted => e
 94:           @reply.fail e.to_s, 1
 95: 
 96:         rescue UnknownRPCAction => e
 97:           @reply.fail e.to_s, 2
 98: 
 99:         rescue MissingRPCData => e
100:           @reply.fail e.to_s, 3
101: 
102:         rescue InvalidRPCData, DDLValidationError => e
103:           @reply.fail e.to_s, 4
104: 
105:         rescue UnknownRPCError => e
106:           Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
107:           Log.error(e.backtrace.join("\n\t"))
108:           @reply.fail e.to_s, 5
109: 
110:         rescue Exception => e
111:           Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
112:           Log.error(e.backtrace.join("\n\t"))
113:           @reply.fail e.to_s, 5
114: 
115:         end
116: 
117:         after_processing_hook
118: 
119:         if @request.should_respond?
120:           return @reply.to_hash
121:         else
122:           log_code(:PLMC35, "Client did not request a response, surpressing reply", :debug)
123:           return nil
124:         end
125:       end
load_ddl() click to toggle source

(Not documented)

    # File lib/mcollective/rpc/agent.rb, line 55
55:       def load_ddl
56:         @ddl = DDL.new(@agent_name, :agent)
57:         @meta = @ddl.meta
58:         @timeout = @meta[:timeout] || 10
59: 
60:       rescue Exception => e
61:         DDL.validation_fail!(:PLMC24, "Failed to load DDL for the '%{agent}' agent, DDLs are required: %{error_class}: %{error}", :error, :agent => @agent_name, :error_class => e.class, :error => e.to_s)
62:       end

Private Instance Methods

after_processing_hook() click to toggle source

Called at the end of processing just before the response gets sent to the middleware.

This gets run outside of the main exception handling block of the agent so you should handle any exceptions you could raise yourself. The reason it is outside of the block is so you’ll have access to even status codes set by the exception handlers. If you do raise an exception it will just be passed onto the runner and processing will fail.

     # File lib/mcollective/rpc/agent.rb, line 356
356:       def after_processing_hook
357:       end
audit_request(msg, connection) click to toggle source

Gets called right after a request was received and calls audit plugins

Agents can disable auditing by just overriding this method with a noop one this might be useful for agents that gets a lot of requests or simply if you do not care for the auditing in a specific agent.

     # File lib/mcollective/rpc/agent.rb, line 364
364:       def audit_request(msg, connection)
365:         PluginManager["rpcaudit_plugin"].audit_request(msg, connection) if @config.rpcaudit
366:       rescue Exception => e
367:         logexception(:PLMC39, "Audit failed with an error, processing the request will continue.", :warn, e)
368:       end
before_processing_hook(msg, connection) click to toggle source

Called just after a message was received from the middleware before it gets passed to the handlers. @request and @reply will already be set, the msg passed is the message as received from the normal mcollective runner and the connection is the actual connector.

     # File lib/mcollective/rpc/agent.rb, line 345
345:       def before_processing_hook(msg, connection)
346:       end
implemented_by(command, type=:json) click to toggle source

handles external actions

     # File lib/mcollective/rpc/agent.rb, line 313
313:       def implemented_by(command, type=:json)
314:         runner = ActionRunner.new(command, request, type)
315: 
316:         res = runner.run
317: 
318:         reply.fail! "Did not receive data from #{command}" unless res.include?(:data)
319:         reply.fail! "Reply data from #{command} is not a Hash" unless res[:data].is_a?(Hash)
320: 
321:         reply.data.merge!(res[:data])
322: 
323:         if res[:exitstatus] > 0
324:           reply.fail "Failed to run #{command}: #{res[:stderr]}", res[:exitstatus]
325:         end
326:       rescue Exception => e
327:         Log.warn("Unhandled #{e.class} exception during #{request.agent}##{request.action}: #{e}")
328:         reply.fail! "Unexpected failure calling #{command}: #{e.class}: #{e}"
329:       end
run(command, options={}) click to toggle source

Runs a command via the MC::Shell wrapper, options are as per MC::Shell

The simplest use is:

  out = ""
  err = ""
  status = run("echo 1", :stdout => out, :stderr => err)

  reply[:out] = out
  reply[:error] = err
  reply[:exitstatus] = status

This can be simplified as:

  reply[:exitstatus] = run("echo 1", :stdout => :out, :stderr => :error)

You can set a command specific environment and cwd:

  run("echo 1", :cwd => "/tmp", :environment => {"FOO" => "BAR"})

This will run ‘echo 1’ from /tmp with FOO=BAR in addition to a setting forcing LC_ALL = C. To prevent LC_ALL from being set either set it specifically or:

  run("echo 1", :cwd => "/tmp", :environment => nil)

Exceptions here will be handled by the usual agent exception handler or any specific one you create, if you dont it will just fall through and be sent to the client.

If the shell handler fails to return a Process::Status instance for exit status this method will return -1 as the exit status

     # File lib/mcollective/rpc/agent.rb, line 195
195:       def run(command, options={})
196:         shellopts = {}
197: 
198:         # force stderr and stdout to be strings as the library
199:         # will append data to them if given using the << method.
200:         #
201:         # if the data pased to :stderr or :stdin is a Symbol
202:         # add that into the reply hash with that Symbol
203:         [:stderr, :stdout].each do |k|
204:           if options.include?(k)
205:             if options[k].is_a?(Symbol)
206:               reply[ options[k] ] = ""
207:               shellopts[k] = reply[ options[k] ]
208:             else
209:               if options[k].respond_to?("<<")
210:                 shellopts[k] = options[k]
211:               else
212:                 reply.fail! "#{k} should support << while calling run(#{command})"
213:               end
214:             end
215:           end
216:         end
217: 
218:         [:stdin, :cwd, :environment, :timeout].each do |k|
219:           if options.include?(k)
220:             shellopts[k] = options[k]
221:           end
222:         end
223: 
224:         shell = Shell.new(command, shellopts)
225: 
226:         shell.runcommand
227: 
228:         if options[:chomp]
229:           shellopts[:stdout].chomp! if shellopts[:stdout].is_a?(String)
230:           shellopts[:stderr].chomp! if shellopts[:stderr].is_a?(String)
231:         end
232: 
233:         shell.status.exitstatus rescue -1
234:       end
shellescape(str) click to toggle source

convenience wrapper around Util#shellescape

     # File lib/mcollective/rpc/agent.rb, line 308
308:       def shellescape(str)
309:         Util.shellescape(str)
310:       end
startup_hook() click to toggle source

Called at the end of the RPC::Agent standard initialize method use this to adjust meta parameters, timeouts and any setup you need to do.

This will not be called right when the daemon starts up, we use lazy loading and initialization so it will only be called the first time a request for this agent arrives.

     # File lib/mcollective/rpc/agent.rb, line 338
338:       def startup_hook
339:       end
validate(key, validation) click to toggle source

Validates a data member, if validation is a regex then it will try to match it else it supports testing object types only:

validate :msg, String validate :msg, /^[w\s]+$/

There are also some special helper validators:

validate :command, :shellsafe validate :command, :ipv6address validate :command, :ipv4address validate :command, :boolean validate :command, [“start”, “stop”]

It will raise appropriate exceptions that the RPC system understand

     # File lib/mcollective/rpc/agent.rb, line 299
299:       def validate(key, validation)
300:         raise MissingRPCData, "please supply a #{key} argument" unless @request.include?(key)
301: 
302:         Validator.validate(@request[key], validation)
303:       rescue ValidatorError => e
304:         raise InvalidRPCData, "Input %s did not pass validation: %s" % [ key, e.message ]
305:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.