Class: MCollective::RPC::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/mcollective/rpc/agent.rb

Overview

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.

Direct Known Subclasses

Agent::Rpcutil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAgent

Returns a new instance of Agent.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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

Instance Attribute Details

#agent_nameObject

Returns the value of attribute agent_name.



34
35
36
# File 'lib/mcollective/rpc/agent.rb', line 34

def agent_name
  @agent_name
end

#configObject (readonly)

Returns the value of attribute config.



35
36
37
# File 'lib/mcollective/rpc/agent.rb', line 35

def config
  @config
end

#ddlObject (readonly)

Returns the value of attribute ddl.



35
36
37
# File 'lib/mcollective/rpc/agent.rb', line 35

def ddl
  @ddl
end

#loggerObject (readonly)

Returns the value of attribute logger.



35
36
37
# File 'lib/mcollective/rpc/agent.rb', line 35

def logger
  @logger
end

#metaObject (readonly)

Returns the value of attribute meta.



35
36
37
# File 'lib/mcollective/rpc/agent.rb', line 35

def meta
  @meta
end

#replyObject

Returns the value of attribute reply.



34
35
36
# File 'lib/mcollective/rpc/agent.rb', line 34

def reply
  @reply
end

#requestObject

Returns the value of attribute request.



34
35
36
# File 'lib/mcollective/rpc/agent.rb', line 34

def request
  @request
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



35
36
37
# File 'lib/mcollective/rpc/agent.rb', line 35

def timeout
  @timeout
end

Class Method Details

.actionsObject

Returns an array of actions this agent support



160
161
162
163
164
# File 'lib/mcollective/rpc/agent.rb', line 160

def self.actions
  public_instance_methods.sort.grep(/_action$/).map do |method|
    $1 if method =~ /(.+)_action$/
  end
end

.activate?Boolean

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

Returns:

  • (Boolean)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/mcollective/rpc/agent.rb', line 139

def self.activate?
  agent_name = self.to_s.split("::").last.downcase
  config = Config.instance

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

  # Check global state to determine if agent should be loaded
  should_activate = config.activate_agents

  # Check agent specific state to determine if agent should be loaded
  should_activate = Util.str_to_bool(config.pluginconf.fetch("#{agent_name}.activate_agent", 
                                     should_activate))

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

  return should_activate
end

Instance Method Details

#handlemsg(msg, connection) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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_ddlObject



52
53
54
55
56
57
58
59
60
# 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