X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;ds=sidebyside;f=doc%2FMCollective%2FRPC%2FAgent.html;fp=doc%2FMCollective%2FRPC%2FAgent.html;h=77d1cd5b71b154004c4314791bfcef75ad2a455a;hb=7c9314f502cde8daad23b61d10b24a542e04154a;hp=0000000000000000000000000000000000000000;hpb=d1f1649ba43c5cbc43c4beb2380096ba051d646a;p=packages%2Fprecise%2Fmcollective.git diff --git a/doc/MCollective/RPC/Agent.html b/doc/MCollective/RPC/Agent.html new file mode 100644 index 0000000..77d1cd5 --- /dev/null +++ b/doc/MCollective/RPC/Agent.html @@ -0,0 +1,1457 @@ + + + + + + + Class: MCollective::RPC::Agent + + + + + + + + + + + +
+
+
+

In Files

+ +
+ + +
+ +
+ + + +
+

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.

+
+ + + +