Class | MCollective::RPC::Client |
In: |
lib/mcollective/rpc/client.rb
|
Parent: | Object |
The main component of the Simple RPC client system, this wraps around MCollective::Client and just brings in a lot of convention and standard approached.
agent | [R] | |
client | [R] | |
config | [RW] | |
ddl | [R] | |
discovery_timeout | [RW] | |
filter | [RW] | |
progress | [RW] | |
stats | [R] | |
timeout | [RW] | |
verbose | [RW] |
Creates a stub for a remote agent, you can pass in an options array in the flags which will then be used else it will just create a default options array with filtering enabled based on the standard command line use.
rpc = RPC::Client.new("rpctest", :configfile => "client.cfg", :options => options)
You typically would not call this directly you‘d use MCollective::RPC#rpcclient instead which is a wrapper around this that can be used as a Mixin
# File lib/mcollective/rpc/client.rb, line 19 19: def initialize(agent, flags = {}) 20: if flags.include?(:options) 21: options = flags[:options] 22: 23: elsif @@initial_options 24: options = Marshal.load(@@initial_options) 25: 26: else 27: oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter") 28: 29: options = oparser.parse do |parser, options| 30: if block_given? 31: yield(parser, options) 32: end 33: 34: Helpers.add_simplerpc_options(parser, options) 35: end 36: 37: @@initial_options = Marshal.dump(options) 38: end 39: 40: @stats = Stats.new 41: @agent = agent 42: @discovery_timeout = options[:disctimeout] 43: @timeout = options[:timeout] 44: @verbose = options[:verbose] 45: @filter = options[:filter] 46: @config = options[:config] 47: @discovered_agents = nil 48: @progress = options[:progress_bar] 49: 50: agent_filter agent 51: 52: @client = client = MCollective::Client.new(@config) 53: @client.options = options 54: 55: # if we can find a DDL for the service override 56: # the timeout of the client so we always magically 57: # wait appropriate amounts of time. 58: # 59: # We do this only if the timeout is the default 5 60: # seconds, so that users cli overrides will still 61: # get applied 62: begin 63: @ddl = DDL.new(agent) 64: @timeout = @ddl.meta[:timeout] if @timeout == 5 65: rescue Exception => e 66: Log.instance.debug("Could not find DDL: #{e}") 67: @ddl = nil 68: end 69: 70: STDERR.sync = true 71: STDOUT.sync = true 72: end
Sets the agent filter
# File lib/mcollective/rpc/client.rb, line 258 258: def agent_filter(agent) 259: @filter["agent"] << agent 260: @filter["agent"].compact! 261: reset 262: end
Sets the class filter
# File lib/mcollective/rpc/client.rb, line 244 244: def class_filter(klass) 245: @filter["cf_class"] << klass 246: @filter["cf_class"].compact! 247: reset 248: end
Constructs custom requests with custom filters and discovery data the idea is that this would be used in web applications where you might be using a cached copy of data provided by a registration agent to figure out on your own what nodes will be responding and what your filter would be.
This will help you essentially short circuit the traditional cycle of:
mc discover / call / wait for discovered nodes
by doing discovery however you like, contructing a filter and a list of nodes you expect responses from.
Other than that it will work exactly like a normal call, blocks will behave the same way, stats will be handled the same way etcetc
If you just wanted to contact one machine for example with a client that already has other filter options setup you can do:
puppet.custom_request("runonce", {}, {:identity => "your.box.com"},
["your.box.com"])
This will do runonce action on just ‘your.box.com’, no discovery will be done and after receiving just one response it will stop waiting for responses
# File lib/mcollective/rpc/client.rb, line 208 208: def custom_request(action, args, expected_agents, filter = {}, &block) 209: @ddl.validate_request(action, args) if @ddl 210: 211: @stats.reset 212: 213: custom_filter = Util.empty_filter 214: custom_options = options.clone 215: 216: # merge the supplied filter with the standard empty one 217: # we could just use the merge method but I want to be sure 218: # we dont merge in stuff that isnt actually valid 219: ["identity", "fact", "agent", "cf_class"].each do |ftype| 220: if filter.include?(ftype) 221: custom_filter[ftype] = [filter[ftype], custom_filter[ftype]].flatten 222: end 223: end 224: 225: # ensure that all filters at least restrict the call to the agent we're a proxy for 226: custom_filter["agent"] << @agent unless custom_filter["agent"].include?(@agent) 227: custom_options[:filter] = custom_filter 228: 229: # Fake out the stats discovery would have put there 230: @stats.discovered_agents([expected_agents].flatten) 231: 232: # Now do a call pretty much exactly like in method_missing except with our own 233: # options and discovery magic 234: if block_given? 235: call_agent(action, args, custom_options, [expected_agents].flatten) do |r| 236: block.call(r) 237: end 238: else 239: call_agent(action, args, custom_options, [expected_agents].flatten) 240: end 241: end
Does discovery based on the filters set, i a discovery was previously done return that else do a new discovery.
Will show a message indicating its doing discovery if running verbose or if the :verbose flag is passed in.
Use reset to force a new discovery
# File lib/mcollective/rpc/client.rb, line 290 290: def discover(flags={}) 291: flags.include?(:verbose) ? verbose = flags[:verbose] : verbose = @verbose 292: 293: if @discovered_agents == nil 294: @stats.time_discovery :start 295: 296: STDERR.print("Determining the amount of hosts matching filter for #{discovery_timeout} seconds .... ") if verbose 297: @discovered_agents = @client.discover(@filter, @discovery_timeout) 298: STDERR.puts(@discovered_agents.size) if verbose 299: 300: @stats.time_discovery :end 301: 302: end 303: 304: @stats.discovered_agents(@discovered_agents) 305: RPC.discovered(@discovered_agents) 306: 307: @discovered_agents 308: end
Sets the fact filter
# File lib/mcollective/rpc/client.rb, line 251 251: def fact_filter(fact, value) 252: @filter["fact"] << {:fact => fact, :value => value} 253: @filter["fact"].compact! 254: reset 255: end
Sets the identity filter
# File lib/mcollective/rpc/client.rb, line 265 265: def identity_filter(identity) 266: @filter["identity"] << identity 267: @filter["identity"].compact! 268: reset 269: end
Magic handler to invoke remote methods
Once the stub is created using the constructor or the RPC#rpcclient helper you can call remote actions easily:
ret = rpc.echo(:msg => "hello world")
This will call the ‘echo’ action of the ‘rpctest’ agent and return the result as an array, the array will be a simplified result set from the usual full MCollective::Client#req with additional error codes and error text:
{
:sender => "remote.box.com", :statuscode => 0, :statusmsg => "OK", :data => "hello world"
}
If :statuscode is 0 then everything went find, if it‘s 1 then you supplied the correct arguments etc but the request could not be completed, you‘ll find a human parsable reason in :statusmsg then.
Codes 2 to 5 maps directly to UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError see below for a description of those, in each case :statusmsg would be the reason for failure.
To get access to the full result of the MCollective::Client#req calls you can pass in a block:
rpc.echo(:msg => "hello world") do |resp| pp resp end
In this case resp will the result from MCollective::Client#req. Instead of returning simple text and codes as above you‘ll also need to handle the following exceptions:
UnknownRPCAction - There is no matching action on the agent MissingRPCData - You did not supply all the needed parameters for the action InvalidRPCData - The data you did supply did not pass validation UnknownRPCError - Some other error prevented the agent from running
During calls a progress indicator will be shown of how many results we‘ve received against how many nodes were discovered, you can disable this by setting progress to false:
rpc.progress = false
This supports a 2nd mode where it will send the SimpleRPC request and never handle the responses. It‘s a bit like UDP, it sends the request with the filter attached and you only get back the requestid, you have no indication about results.
You can invoke this using:
puts rpc.echo(:process_results => false)
This will output just the request id.
# File lib/mcollective/rpc/client.rb, line 163 163: def method_missing(method_name, *args, &block) 164: # set args to an empty hash if nothings given 165: args = args[0] 166: args = {} if args.nil? 167: 168: action = method_name.to_s 169: 170: @stats.reset 171: 172: @ddl.validate_request(action, args) if @ddl 173: 174: # Normal agent requests as per client.action(args) 175: if block_given? 176: call_agent(action, args, options) do |r| 177: block.call(r) 178: end 179: else 180: call_agent(action, args, options) 181: end 182: end
Creates a suitable request hash for the SimpleRPC agent.
You‘d use this if you ever wanted to take care of sending requests on your own - perhaps via Client#sendreq if you didn‘t care for responses.
In that case you can just do:
msg = your_rpc.new_request("some_action", :foo => :bar) filter = your_rpc.filter your_rpc.client.sendreq(msg, msg[:agent], filter)
This will send a SimpleRPC request to the action some_action with arguments :foo = :bar, it will return immediately and you will have no indication at all if the request was receieved or not
Clearly the use of this technique should be limited and done only if your code requires such a thing
# File lib/mcollective/rpc/client.rb, line 102 102: def new_request(action, data) 103: callerid = PluginManager["security_plugin"].callerid 104: 105: {:agent => @agent, 106: :action => action, 107: :caller => callerid, 108: :data => data} 109: end
Provides a normal options hash like you would get from Optionparser
# File lib/mcollective/rpc/client.rb, line 312 312: def options 313: {:disctimeout => @discovery_timeout, 314: :timeout => @timeout, 315: :verbose => @verbose, 316: :filter => @filter, 317: :config => @config} 318: end
Resets various internal parts of the class, most importantly it clears out the cached discovery
# File lib/mcollective/rpc/client.rb, line 273 273: def reset 274: @discovered_agents = nil 275: end