class 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

agent_name[RW]
config[R]
ddl[R]
logger[R]
meta[R]
reply[RW]
request[RW]
timeout[R]

Public Class Methods

actions() click to toggle source

Returns an array of actions this agent support

# File lib/mcollective/rpc/agent.rb, line 154
def self.actions
  public_instance_methods.sort.grep(/_action$/).map do |method|
    $1 if method =~ /(.+)_action$/
  end
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 139
def self.activate?
  agent_name = self.to_s.split("::").last.downcase

  Log.debug("Starting default activation checks for #{agent_name}")

  should_activate = Util.str_to_bool(Config.instance.pluginconf.fetch("#{agent_name}.activate_agent", true))

  unless should_activate
    Log.debug("Found plugin configuration '#{agent_name}.activate_agent' with value '#{should_activate}'")
  end

  return should_activate
end
new() click to toggle source
# File lib/mcollective/rpc/agent.rb, line 37
def initialize
  @agent_name = self.class.to_s.split("::").last.downcase

  load_ddl

  @logger = Log.instance
  @config = Config.instance

  # if we have a global authorization provider enable it
  # plugins can still override it per plugin
  self.class.authorized_by(@config.rpcauthprovider) if @config.rpcauthorization

  startup_hook
end

Public Instance Methods

handlemsg(msg, connection) click to toggle source
# File lib/mcollective/rpc/agent.rb, line 62
def handlemsg(msg, connection)
  @request = RPC::Request.new(msg, @ddl)
  @reply = RPC::Reply.new(@request.action, @ddl)

  begin
    # Incoming requests need to be validated against the DDL thus reusing
    # all the work users put into creating DDLs and creating a consistent
    # quality of input validation everywhere with the a simple once off
    # investment of writing a DDL
    @request.validate!

    # Calls the authorization plugin if any is defined
    # if this raises an exception we wil just skip processing this
    # message
    authorization_hook(@request) if respond_to?("authorization_hook")

    # Audits the request, currently continues processing the message
    # we should make this a configurable so that an audit failure means
    # a message wont be processed by this node depending on config
    audit_request(@request, connection)

    before_processing_hook(msg, connection)

    if respond_to?("#{@request.action}_action")
      send("#{@request.action}_action")
    else
      raise UnknownRPCAction, "Unknown action '#{@request.action}' for agent '#{@request.agent}'"
    end
  rescue RPCAborted => e
    @reply.fail e.to_s, 1

  rescue UnknownRPCAction => e
    @reply.fail e.to_s, 2

  rescue MissingRPCData => e
    @reply.fail e.to_s, 3

  rescue InvalidRPCData, DDLValidationError => e
    @reply.fail e.to_s, 4

  rescue UnknownRPCError => e
    Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
    Log.error(e.backtrace.join("\n\t"))
    @reply.fail e.to_s, 5

  rescue Exception => e
    Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
    Log.error(e.backtrace.join("\n\t"))
    @reply.fail e.to_s, 5

  end

  after_processing_hook

  if @request.should_respond?
    return @reply.to_hash
  else
    Log.debug("Client did not request a response, surpressing reply")
    return nil
  end
end
load_ddl() click to toggle source
# File lib/mcollective/rpc/agent.rb, line 52
def load_ddl
  @ddl = DDL.new(@agent_name, :agent)
  @meta = @ddl.meta
  @timeout = @meta[:timeout] || 10

rescue Exception => e
  Log.error("Failed to load DDL for the '%s' agent, DDLs are required: %s: %s" % [@agent_name, e.class, e.to_s])
  raise DDLValidationError
end