mcollective-client 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (140) hide show
  1. data/lib/mcollective.rb +32 -23
  2. data/lib/mcollective/agent.rb +5 -0
  3. data/lib/mcollective/agents.rb +5 -16
  4. data/lib/mcollective/aggregate.rb +61 -0
  5. data/lib/mcollective/aggregate/base.rb +40 -0
  6. data/lib/mcollective/aggregate/result.rb +9 -0
  7. data/lib/mcollective/aggregate/result/base.rb +25 -0
  8. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  9. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  10. data/lib/mcollective/application.rb +7 -4
  11. data/lib/mcollective/applications.rb +3 -14
  12. data/lib/mcollective/cache.rb +145 -0
  13. data/lib/mcollective/client.rb +10 -87
  14. data/lib/mcollective/config.rb +22 -8
  15. data/lib/mcollective/data.rb +87 -0
  16. data/lib/mcollective/data/base.rb +67 -0
  17. data/lib/mcollective/data/result.rb +40 -0
  18. data/lib/mcollective/ddl.rb +113 -0
  19. data/lib/mcollective/ddl/agentddl.rb +185 -0
  20. data/lib/mcollective/ddl/base.rb +220 -0
  21. data/lib/mcollective/ddl/dataddl.rb +56 -0
  22. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  23. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  24. data/lib/mcollective/discovery.rb +143 -0
  25. data/lib/mcollective/generators.rb +7 -0
  26. data/lib/mcollective/generators/agent_generator.rb +51 -0
  27. data/lib/mcollective/generators/base.rb +46 -0
  28. data/lib/mcollective/generators/data_generator.rb +51 -0
  29. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  30. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  31. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  32. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  33. data/lib/mcollective/logger/console_logger.rb +15 -15
  34. data/lib/mcollective/matcher.rb +167 -0
  35. data/lib/mcollective/matcher/parser.rb +60 -25
  36. data/lib/mcollective/matcher/scanner.rb +156 -78
  37. data/lib/mcollective/message.rb +47 -6
  38. data/lib/mcollective/monkey_patches.rb +17 -0
  39. data/lib/mcollective/optionparser.rb +18 -1
  40. data/lib/mcollective/pluginmanager.rb +3 -3
  41. data/lib/mcollective/pluginpackager.rb +10 -3
  42. data/lib/mcollective/pluginpackager/agent_definition.rb +28 -20
  43. data/lib/mcollective/pluginpackager/standard_definition.rb +11 -9
  44. data/lib/mcollective/registration/base.rb +3 -1
  45. data/lib/mcollective/rpc.rb +18 -24
  46. data/lib/mcollective/rpc/agent.rb +37 -113
  47. data/lib/mcollective/rpc/client.rb +186 -64
  48. data/lib/mcollective/rpc/helpers.rb +42 -80
  49. data/lib/mcollective/rpc/progress.rb +3 -3
  50. data/lib/mcollective/rpc/reply.rb +37 -13
  51. data/lib/mcollective/rpc/request.rb +17 -6
  52. data/lib/mcollective/rpc/result.rb +9 -5
  53. data/lib/mcollective/rpc/stats.rb +71 -24
  54. data/lib/mcollective/security/base.rb +41 -34
  55. data/lib/mcollective/shell.rb +1 -1
  56. data/lib/mcollective/ssl.rb +34 -0
  57. data/lib/mcollective/util.rb +194 -23
  58. data/lib/mcollective/validator.rb +80 -0
  59. data/spec/fixtures/util/1.in +10 -0
  60. data/spec/fixtures/util/1.out +10 -0
  61. data/spec/fixtures/util/2.in +1 -0
  62. data/spec/fixtures/util/2.out +1 -0
  63. data/spec/fixtures/util/3.in +1 -0
  64. data/spec/fixtures/util/3.out +2 -0
  65. data/spec/fixtures/util/4.in +5 -0
  66. data/spec/fixtures/util/4.out +9 -0
  67. data/spec/spec.opts +1 -1
  68. data/spec/spec_helper.rb +2 -0
  69. data/spec/unit/agents_spec.rb +34 -19
  70. data/spec/unit/aggregate/base_spec.rb +57 -0
  71. data/spec/unit/aggregate/result/base_spec.rb +28 -0
  72. data/spec/unit/aggregate/result/collection_result_spec.rb +18 -0
  73. data/spec/unit/aggregate/result/numeric_result_spec.rb +22 -0
  74. data/spec/unit/aggregate_spec.rb +110 -0
  75. data/spec/unit/application_spec.rb +8 -3
  76. data/spec/unit/applications_spec.rb +2 -2
  77. data/spec/unit/cache_spec.rb +115 -0
  78. data/spec/unit/client_spec.rb +78 -0
  79. data/spec/unit/config_spec.rb +32 -34
  80. data/spec/unit/data/base_spec.rb +90 -0
  81. data/spec/unit/data/result_spec.rb +64 -0
  82. data/spec/unit/data_spec.rb +158 -0
  83. data/spec/unit/ddl/agentddl_spec.rb +217 -0
  84. data/spec/unit/{rpc/ddl_spec.rb → ddl/base_spec.rb} +238 -224
  85. data/spec/unit/ddl/dataddl_spec.rb +65 -0
  86. data/spec/unit/ddl/discoveryddl_spec.rb +58 -0
  87. data/spec/unit/ddl_spec.rb +84 -0
  88. data/spec/unit/discovery_spec.rb +196 -0
  89. data/spec/unit/facts/base_spec.rb +1 -1
  90. data/spec/unit/generators/agent_generator_spec.rb +72 -0
  91. data/spec/unit/generators/base_spec.rb +83 -0
  92. data/spec/unit/generators/data_generator_spec.rb +37 -0
  93. data/spec/unit/generators/snippets/agent_ddl +19 -0
  94. data/spec/unit/generators/snippets/data_ddl +20 -0
  95. data/spec/unit/logger/console_logger_spec.rb +76 -0
  96. data/spec/unit/logger/syslog_logger_spec.rb +2 -2
  97. data/spec/unit/matcher/parser_spec.rb +27 -10
  98. data/spec/unit/matcher/scanner_spec.rb +108 -5
  99. data/spec/unit/matcher_spec.rb +260 -0
  100. data/spec/unit/message_spec.rb +35 -13
  101. data/spec/unit/optionparser_spec.rb +2 -2
  102. data/spec/unit/pluginpackager/agent_definition_spec.rb +59 -42
  103. data/spec/unit/pluginpackager/standard_definition_spec.rb +10 -8
  104. data/spec/unit/pluginpackager_spec.rb +131 -0
  105. data/spec/unit/plugins/mcollective/aggregate/average_spec.rb +45 -0
  106. data/spec/unit/plugins/mcollective/aggregate/sum_spec.rb +31 -0
  107. data/spec/unit/plugins/mcollective/aggregate/summary_spec.rb +45 -0
  108. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +1 -1
  109. data/spec/unit/plugins/mcollective/connector/rabbitmq_spec.rb +478 -0
  110. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +2 -0
  111. data/spec/unit/plugins/mcollective/data/agent_data_spec.rb +43 -0
  112. data/spec/unit/plugins/mcollective/data/fstat_data_spec.rb +135 -0
  113. data/spec/unit/plugins/mcollective/discovery/flatfile_spec.rb +48 -0
  114. data/spec/unit/plugins/mcollective/discovery/mc_spec.rb +40 -0
  115. data/spec/unit/plugins/mcollective/packagers/debpackage_packager_spec.rb +41 -15
  116. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +1 -1
  117. data/spec/unit/plugins/mcollective/packagers/rpmpackage_packager_spec.rb +22 -38
  118. data/spec/unit/plugins/mcollective/validator/array_validator_spec.rb +19 -0
  119. data/spec/unit/plugins/mcollective/validator/ipv4address_validator_spec.rb +19 -0
  120. data/spec/unit/plugins/mcollective/validator/ipv6address_validator_spec.rb +19 -0
  121. data/spec/unit/plugins/mcollective/validator/length_validator_spec.rb +19 -0
  122. data/spec/unit/plugins/mcollective/validator/regex_validator_spec.rb +19 -0
  123. data/spec/unit/plugins/mcollective/validator/shellsafe_validator_spec.rb +21 -0
  124. data/spec/unit/plugins/mcollective/validator/typecheck_validator_spec.rb +23 -0
  125. data/spec/unit/registration/base_spec.rb +1 -1
  126. data/spec/unit/rpc/actionrunner_spec.rb +2 -2
  127. data/spec/unit/rpc/agent_spec.rb +41 -65
  128. data/spec/unit/rpc/client_spec.rb +430 -134
  129. data/spec/unit/rpc/reply_spec.rb +31 -1
  130. data/spec/unit/rpc/request_spec.rb +33 -12
  131. data/spec/unit/rpc/result_spec.rb +7 -0
  132. data/spec/unit/rpc/stats_spec.rb +14 -14
  133. data/spec/unit/rpc_spec.rb +16 -0
  134. data/spec/unit/security/base_spec.rb +8 -8
  135. data/spec/unit/ssl_spec.rb +20 -2
  136. data/spec/unit/string_spec.rb +15 -0
  137. data/spec/unit/util_spec.rb +141 -21
  138. data/spec/unit/validator_spec.rb +67 -0
  139. metadata +145 -7
  140. data/lib/mcollective/rpc/ddl.rb +0 -258
@@ -10,24 +10,17 @@ module MCollective
10
10
  # usage would be:
11
11
  #
12
12
  # module MCollective
13
- # module Agent
14
- # class Helloworld<RPC::Agent
15
- # matadata :name => "Test SimpleRPC Agent",
16
- # :description => "A simple test",
17
- # :author => "You",
18
- # :license => "1.1",
19
- # :url => "http://your.com/,
20
- # :timeout => 60
21
- #
22
- # action "hello" do
23
- # reply[:msg] = "Hello #{request[:name]}"
24
- # end
13
+ # module Agent
14
+ # class Helloworld<RPC::Agent
15
+ # action "hello" do
16
+ # reply[:msg] = "Hello #{request[:name]}"
17
+ # end
25
18
  #
26
- # action "foo" do
27
- # implemented_by "/some/script.sh"
28
- # end
19
+ # action "foo" do
20
+ # implemented_by "/some/script.sh"
29
21
  # end
30
- # end
22
+ # end
23
+ # end
31
24
  # end
32
25
  #
33
26
  # If you wish to implement the logic for an action using an external script use the
@@ -38,32 +31,16 @@ module MCollective
38
31
  #
39
32
  # We also currently have the validation code in here, this will be moved to plugins soon.
40
33
  class Agent
41
- attr_accessor :meta, :reply, :request
42
- attr_reader :logger, :config, :timeout, :ddl
34
+ attr_accessor :reply, :request, :agent_name
35
+ attr_reader :logger, :config, :timeout, :ddl, :meta
43
36
 
44
37
  def initialize
45
- # Default meta data unset
46
- @meta = {:timeout => 10,
47
- :name => "Unknown",
48
- :description => "Unknown",
49
- :author => "Unknown",
50
- :license => "Unknown",
51
- :version => "Unknown",
52
- :url => "Unknown"}
53
-
54
- @timeout = meta[:timeout] || 10
55
- @logger = Log.instance
56
- @config = Config.instance
57
38
  @agent_name = self.class.to_s.split("::").last.downcase
58
39
 
59
- # Loads the DDL so we can later use it for validation
60
- # and help generation
61
- begin
62
- @ddl = DDL.new(@agent_name)
63
- rescue Exception => e
64
- Log.debug("Failed to load DDL for agent: #{e.class}: #{e}")
65
- @ddl = nil
66
- end
40
+ load_ddl
41
+
42
+ @logger = Log.instance
43
+ @config = Config.instance
67
44
 
68
45
  # if we have a global authorization provider enable it
69
46
  # plugins can still override it per plugin
@@ -72,17 +49,32 @@ module MCollective
72
49
  startup_hook
73
50
  end
74
51
 
52
+ def load_ddl
53
+ @ddl = DDL.new(@agent_name, :agent)
54
+ @meta = @ddl.meta
55
+ @timeout = @meta[:timeout] || 10
56
+
57
+ rescue Exception => e
58
+ Log.error("Failed to load DDL for the '%s' agent, DDLs are required: %s: %s" % [@agent_name, e.class, e.to_s])
59
+ raise DDLValidationError
60
+ end
61
+
75
62
  def handlemsg(msg, connection)
76
- @request = RPC.request(msg)
77
- @reply = RPC.reply
63
+ @request = RPC::Request.new(msg, @ddl)
64
+ @reply = RPC::Reply.new(@request.action, @ddl)
78
65
 
79
66
  begin
67
+ # Incoming requests need to be validated against the DDL thus reusing
68
+ # all the work users put into creating DDLs and creating a consistant
69
+ # quality of input validation everywhere with the a simple once off
70
+ # investment of writing a DDL
71
+ @request.validate!
72
+
80
73
  # Calls the authorization plugin if any is defined
81
74
  # if this raises an exception we wil just skip processing this
82
75
  # message
83
76
  authorization_hook(@request) if respond_to?("authorization_hook")
84
77
 
85
-
86
78
  # Audits the request, currently continues processing the message
87
79
  # we should make this a configurable so that an audit failure means
88
80
  # a message wont be processed by this node depending on config
@@ -157,21 +149,6 @@ module MCollective
157
149
  return true
158
150
  end
159
151
 
160
- # Generates help using the template based on the data
161
- # created with metadata and input
162
- def self.help(template)
163
- if @ddl
164
- @ddl.help(template)
165
- else
166
- "No DDL defined"
167
- end
168
- end
169
-
170
- # to auto generate help
171
- def help
172
- self.help("#{@config[:configdir]}/rpc-help.erb")
173
- end
174
-
175
152
  # Returns an array of actions this agent support
176
153
  def self.actions
177
154
  public_instance_methods.sort.grep(/_action$/).map do |method|
@@ -179,7 +156,6 @@ module MCollective
179
156
  end
180
157
  end
181
158
 
182
-
183
159
  private
184
160
  # Runs a command via the MC::Shell wrapper, options are as per MC::Shell
185
161
  #
@@ -255,20 +231,7 @@ module MCollective
255
231
 
256
232
  # Registers meta data for the introspection hash
257
233
  def self.metadata(data)
258
- [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
259
- raise "Metadata needs a :#{arg}" unless data.include?(arg)
260
- end
261
-
262
- # Our old style agents were able to do all sorts of things to the meta
263
- # data during startup_hook etc, don't really want that but also want
264
- # backward compat.
265
- #
266
- # Here if you're using the new metadata way this replaces the getter
267
- # with one that always return the same data, setter will still work but
268
- # wont actually do anything of note.
269
- define_method("meta") {
270
- data
271
- }
234
+ Log.warn("%s: setting meta data in agents have been deprecated, DDL files are now being used for this information." % File.basename(caller.first))
272
235
  end
273
236
 
274
237
  # Creates the needed activate? class in a manner similar to the other
@@ -327,51 +290,12 @@ module MCollective
327
290
  # validate :command, ["start", "stop"]
328
291
  #
329
292
  # It will raise appropriate exceptions that the RPC system understand
330
- #
331
- # TODO: this should be plugins, 1 per validatin method so users can add their own
332
- # at the moment i have it here just to proof the point really
333
293
  def validate(key, validation)
334
294
  raise MissingRPCData, "please supply a #{key} argument" unless @request.include?(key)
335
295
 
336
- if validation.is_a?(Regexp)
337
- raise InvalidRPCData, "#{key} should match #{validation}" unless @request[key].match(validation)
338
-
339
- elsif validation.is_a?(Symbol)
340
- case validation
341
- when :shellsafe
342
- raise InvalidRPCData, "#{key} should be a String" unless @request[key].is_a?(String)
343
-
344
- ['`', '$', ';', '|', '&&', '>', '<'].each do |chr|
345
- raise InvalidRPCData, "#{key} should not have #{chr} in it" if @request[key].match(Regexp.escape(chr))
346
- end
347
-
348
- when :ipv6address
349
- begin
350
- require 'ipaddr'
351
- ip = IPAddr.new(@request[key])
352
- raise InvalidRPCData, "#{key} should be an ipv6 address" unless ip.ipv6?
353
- rescue
354
- raise InvalidRPCData, "#{key} should be an ipv6 address"
355
- end
356
-
357
- when :ipv4address
358
- begin
359
- require 'ipaddr'
360
- ip = IPAddr.new(@request[key])
361
- raise InvalidRPCData, "#{key} should be an ipv4 address" unless ip.ipv4?
362
- rescue
363
- raise InvalidRPCData, "#{key} should be an ipv4 address"
364
- end
365
-
366
- when :boolean
367
- raise InvalidRPCData, "#{key} should be boolean" unless [TrueClass, FalseClass].include?(@request[key].class)
368
- end
369
- elsif validation.is_a?(Array)
370
- raise InvalidRPCData, "#{key} should be one of %s" % [ validation.join(", ") ] unless validation.include?(@request[key])
371
-
372
- else
373
- raise InvalidRPCData, "#{key} should be a #{validation}" unless @request[key].is_a?(validation)
374
- end
296
+ Validator.validate(@request[key], validation)
297
+ rescue ValidatorError => e
298
+ raise InvalidRPCData, "Input %s did not pass validation: %s" % [ key, e.message ]
375
299
  end
376
300
 
377
301
  # convenience wrapper around Util#shellescape
@@ -3,8 +3,9 @@ module MCollective
3
3
  # The main component of the Simple RPC client system, this wraps around MCollective::Client
4
4
  # and just brings in a lot of convention and standard approached.
5
5
  class Client
6
- attr_accessor :discovery_timeout, :timeout, :verbose, :filter, :config, :progress, :ttl, :reply_to
6
+ attr_accessor :timeout, :verbose, :filter, :config, :progress, :ttl, :reply_to
7
7
  attr_reader :client, :stats, :ddl, :agent, :limit_targets, :limit_method, :output_format, :batch_size, :batch_sleep_time, :batch_mode
8
+ attr_reader :discovery_options, :discovery_method, :limit_seed
8
9
 
9
10
  @@initial_options = nil
10
11
 
@@ -37,20 +38,24 @@ module MCollective
37
38
  @@initial_options = Marshal.dump(initial_options)
38
39
  end
39
40
 
41
+ @initial_options = initial_options
40
42
  @stats = Stats.new
41
43
  @agent = agent
42
- @discovery_timeout = initial_options[:disctimeout]
43
- @timeout = initial_options[:timeout]
44
+ @timeout = initial_options[:timeout] || 5
44
45
  @verbose = initial_options[:verbose]
45
- @filter = initial_options[:filter]
46
+ @filter = initial_options[:filter] || Util.empty_filter
46
47
  @config = initial_options[:config]
47
48
  @discovered_agents = nil
48
49
  @progress = initial_options[:progress_bar]
49
50
  @limit_targets = initial_options[:mcollective_limit_targets]
50
51
  @limit_method = Config.instance.rpclimitmethod
52
+ @limit_seed = initial_options[:limit_seed] || nil
51
53
  @output_format = initial_options[:output_format] || :console
52
54
  @force_direct_request = false
53
55
  @reply_to = initial_options[:reply_to]
56
+ @discovery_method = initial_options[:discovery_method] || Config.instance.default_discovery_method
57
+ @discovery_options = initial_options[:discovery_options] || []
58
+ @force_display_mode = initial_options[:force_display_mode] || false
54
59
 
55
60
  @batch_size = Integer(initial_options[:batch_size] || 0)
56
61
  @batch_sleep_time = Float(initial_options[:batch_sleep_time] || 1)
@@ -61,6 +66,8 @@ module MCollective
61
66
  @client = MCollective::Client.new(@config)
62
67
  @client.options = initial_options
63
68
 
69
+ @discovery_timeout = discovery_timeout
70
+
64
71
  @collective = @client.collective
65
72
  @ttl = initial_options[:ttl] || Config.instance.ttl
66
73
 
@@ -77,13 +84,11 @@ module MCollective
77
84
  # We do this only if the timeout is the default 5
78
85
  # seconds, so that users cli overrides will still
79
86
  # get applied
80
- begin
81
- @ddl = DDL.new(agent)
82
- @timeout = @ddl.meta[:timeout] + @discovery_timeout if @timeout == 5
83
- rescue Exception => e
84
- Log.debug("Could not find DDL: #{e}")
85
- @ddl = nil
86
- end
87
+ #
88
+ # DDLs are required, failure to find a DDL is fatal
89
+ @ddl = DDL.new(agent)
90
+ @stats.ddl = @ddl
91
+ @timeout = @ddl.meta[:timeout] + @discovery_timeout if @timeout == 5
87
92
 
88
93
  # allows stderr and stdout to be overridden for testing
89
94
  # but also for web apps that might not want a bunch of stuff
@@ -110,11 +115,7 @@ module MCollective
110
115
 
111
116
  # Returns help for an agent if a DDL was found
112
117
  def help(template)
113
- if @ddl
114
- @ddl.help(template)
115
- else
116
- return "Can't find DDL for agent '#{@agent}'"
117
- end
118
+ @ddl.help(template)
118
119
  end
119
120
 
120
121
  # Creates a suitable request hash for the SimpleRPC agent.
@@ -215,7 +216,7 @@ module MCollective
215
216
 
216
217
  @stats.reset
217
218
 
218
- @ddl.validate_request(action, args) if @ddl
219
+ @ddl.validate_rpc_request(action, args) if @ddl
219
220
 
220
221
  # if a global batch size is set just use that else set it
221
222
  # in the case that it was passed as an argument
@@ -272,7 +273,7 @@ module MCollective
272
273
  # request which technically does not need filters. If you try to use this
273
274
  # mode with direct addressing disabled an exception will be raise
274
275
  def custom_request(action, args, expected_agents, filter = {}, &block)
275
- @ddl.validate_request(action, args) if @ddl
276
+ @ddl.validate_rpc_request(action, args) if @ddl
276
277
 
277
278
  if filter == {} && !Config.instance.direct_addressing
278
279
  raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes"
@@ -319,6 +320,30 @@ module MCollective
319
320
  end
320
321
  end
321
322
 
323
+ def discovery_timeout
324
+ return @initial_options[:disctimeout] if @initial_options[:disctimeout]
325
+ return @client.discoverer.ddl.meta[:timeout]
326
+ end
327
+
328
+ def discovery_method=(method)
329
+ @discovery_method = method
330
+
331
+ if @initial_options[:discovery_options]
332
+ @discovery_options = @initial_options[:discovery_options]
333
+ else
334
+ @discovery_options.clear
335
+ end
336
+
337
+ @client.options = options
338
+ @discovery_timeout = discovery_timeout
339
+ reset
340
+ end
341
+
342
+ def discovery_options=(options)
343
+ @discovery_options = [options].flatten
344
+ reset
345
+ end
346
+
322
347
  # Sets the class filter
323
348
  def class_filter(klass)
324
349
  @filter["cf_class"] << klass
@@ -359,7 +384,7 @@ module MCollective
359
384
 
360
385
  # Set a compound filter
361
386
  def compound_filter(filter)
362
- @filter["compound"] << Matcher::Parser.new(filter).execution_stack
387
+ @filter["compound"] << Matcher.create_compound_callstack(filter)
363
388
  reset
364
389
  end
365
390
 
@@ -431,8 +456,12 @@ module MCollective
431
456
  # only really be for a few -I's on the CLI
432
457
  #
433
458
  # For safety we leave the filter in place for now, that way we can support this
434
- # enhancement also in broadcast mode
435
- elsif options[:filter]["identity"].size > 0
459
+ # enhancement also in broadcast mode.
460
+ #
461
+ # This is only needed for the 'mc' discovery method, other methods might change
462
+ # the concept of identity to mean something else so we should pass the full
463
+ # identity filter to them
464
+ elsif options[:filter]["identity"].size > 0 && @discovery_method == "mc"
436
465
  regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size
437
466
 
438
467
  if regex_filters == 0
@@ -446,21 +475,39 @@ module MCollective
446
475
  unless @discovered_agents
447
476
  @stats.time_discovery :start
448
477
 
449
- @stderr.print("Determining the amount of hosts matching filter for #{discovery_timeout} seconds .... ") if verbose
478
+ @client.options = options
479
+
480
+ # if compound filters are used the only real option is to use the mc
481
+ # discovery plugin since its the only capable of using data queries etc
482
+ # and we do not want to degrade that experience just to allow compounds
483
+ # on other discovery plugins the UX would be too bad raising complex sets
484
+ # of errors etc.
485
+ @client.discoverer.force_discovery_method_by_filter(options[:filter])
486
+
487
+ if verbose
488
+ actual_timeout = @client.discoverer.discovery_timeout(discovery_timeout, options[:filter])
489
+
490
+ if actual_timeout > 0
491
+ @stderr.print("Discovering hosts using the %s method for %d second(s) .... " % [@client.discoverer.discovery_method, actual_timeout])
492
+ else
493
+ @stderr.print("Discovering hosts using the %s method .... " % [@client.discoverer.discovery_method])
494
+ end
495
+ end
450
496
 
451
497
  # if the requested limit is a pure number and not a percent
452
498
  # and if we're configured to use the first found hosts as the
453
499
  # limit method then pass in the limit thus minimizing the amount
454
500
  # of work we do in the discover phase and speeding it up significantly
455
501
  if @limit_method == :first and @limit_targets.is_a?(Fixnum)
456
- @discovered_agents = @client.discover(@filter, @discovery_timeout, @limit_targets)
502
+ @discovered_agents = @client.discover(@filter, discovery_timeout, @limit_targets)
457
503
  else
458
- @discovered_agents = @client.discover(@filter, @discovery_timeout)
504
+ @discovered_agents = @client.discover(@filter, discovery_timeout)
459
505
  end
460
506
 
461
- @force_direct_request = false
462
507
  @stderr.puts(@discovered_agents.size) if verbose
463
508
 
509
+ @force_direct_request = @client.discoverer.force_direct_mode?
510
+
464
511
  @stats.time_discovery :end
465
512
  end
466
513
 
@@ -480,13 +527,19 @@ module MCollective
480
527
  :collective => @collective,
481
528
  :output_format => @output_format,
482
529
  :ttl => @ttl,
530
+ :discovery_method => @discovery_method,
531
+ :discovery_options => @discovery_options,
532
+ :force_display_mode => @force_display_mode,
483
533
  :config => @config}
484
534
  end
485
535
 
486
536
  # Sets the collective we are communicating with
487
537
  def collective=(c)
538
+ raise "Unknown collective #{c}" unless Config.instance.collectives.include?(c)
539
+
488
540
  @collective = c
489
- @client.options[:collective] = c
541
+ @client.options = options
542
+ reset
490
543
  end
491
544
 
492
545
  # Sets and sanity checks the limit_targets variable
@@ -529,7 +582,6 @@ module MCollective
529
582
  @batch_sleep_time = Float(time)
530
583
  end
531
584
 
532
- private
533
585
  # Pick a number of nodes from the discovered nodes
534
586
  #
535
587
  # The count should be a string that can be either
@@ -542,12 +594,14 @@ module MCollective
542
594
  # - :first would be a simple way to do a distance based
543
595
  # selection
544
596
  # - anything else will just pick one at random
597
+ # - if random chosen, and batch-seed set, then set srand
598
+ # for the generator, and reset afterwards
545
599
  def pick_nodes_from_discovered(count)
546
600
  if count =~ /%$/
547
- pct = (discover.size * (count.to_f / 100)).to_i
601
+ pct = Integer((discover.size * (count.to_f / 100)))
548
602
  pct == 0 ? count = 1 : count = pct
549
603
  else
550
- count = count.to_i
604
+ count = Integer(count)
551
605
  end
552
606
 
553
607
  return discover if discover.size <= count
@@ -557,16 +611,58 @@ module MCollective
557
611
  if @limit_method == :first
558
612
  return discover[0, count]
559
613
  else
614
+ # we delete from the discovered list because we want
615
+ # to be sure there is no chance that the same node will
616
+ # be randomly picked twice. So we have to clone the
617
+ # discovered list else this method will only ever work
618
+ # once per discovery cycle and not actually return the
619
+ # right nodes.
620
+ haystack = discover.clone
621
+
622
+ if @limit_seed
623
+ haystack.sort!
624
+ srand(@limit_seed)
625
+ end
626
+
560
627
  count.times do
561
- rnd = rand(discover.size)
562
- result << discover[rnd]
563
- discover.delete_at(rnd)
628
+ rnd = rand(haystack.size)
629
+ result << haystack.delete_at(rnd)
564
630
  end
631
+
632
+ # Reset random number generator to fresh seed
633
+ # As our seed from options is most likely short
634
+ srand if @limit_seed
565
635
  end
566
636
 
567
637
  [result].flatten
568
638
  end
569
639
 
640
+ def load_aggregate_functions(action, ddl)
641
+ return nil unless ddl
642
+ return nil unless ddl.action_interface(action).keys.include?(:aggregate)
643
+
644
+ return Aggregate.new(ddl.action_interface(action))
645
+
646
+ rescue => e
647
+ Log.error("Failed to load aggregate functions, calculating summaries disabled: %s: %s (%s)" % [e.backtrace.first, e.to_s, e.class])
648
+ return nil
649
+ end
650
+
651
+ def aggregate_reply(reply, aggregate)
652
+ return nil unless aggregate
653
+
654
+ aggregate.call_functions(reply)
655
+ return aggregate
656
+ rescue Exception => e
657
+ Log.error("Failed to calculate aggregate summaries for reply from %s, calculating summaries disabled: %s: %s (%s)" % [reply[:senderid], e.backtrace.first, e.to_s, e.class])
658
+ return nil
659
+ end
660
+
661
+ def rpc_result_from_reply(agent, action, reply)
662
+ Result.new(agent, action, {:sender => reply[:senderid], :statuscode => reply[:body][:statuscode],
663
+ :statusmsg => reply[:body][:statusmsg], :data => reply[:body][:data]})
664
+ end
665
+
570
666
  # for requests that do not care for results just
571
667
  # return the request id and don't do any of the
572
668
  # response processing.
@@ -576,7 +672,7 @@ module MCollective
576
672
  #
577
673
  # Should only be called via method_missing
578
674
  def fire_and_forget_request(action, args, filter=nil)
579
- @ddl.validate_request(action, args) if @ddl
675
+ @ddl.validate_rpc_request(action, args) if @ddl
580
676
 
581
677
  req = new_request(action.to_s, args)
582
678
 
@@ -607,31 +703,43 @@ module MCollective
607
703
  @force_direct_request = true
608
704
 
609
705
  discovered = discover
610
- result = []
706
+ results = []
611
707
  respcount = 0
612
708
 
613
709
  if discovered.size > 0
614
710
  req = new_request(action.to_s, args)
615
711
 
712
+ aggregate = load_aggregate_functions(action, @ddl)
713
+
616
714
  if @progress && !block_given?
617
715
  twirl = Progress.new
618
716
  @stdout.puts
619
717
  @stdout.print twirl.twirl(respcount, discovered.size)
620
718
  end
621
719
 
720
+ @stats.requestid = nil
721
+
622
722
  discovered.in_groups_of(batch_size) do |hosts, last_batch|
623
723
  message = Message.new(req, nil, {:agent => @agent, :type => :direct_request, :collective => @collective, :filter => opts[:filter], :options => opts})
724
+
725
+ # first time round we let the Message object create a request id
726
+ # we then re-use it for future requests to keep auditing sane etc
727
+ @stats.requestid = message.create_reqid unless @stats.requestid
728
+ message.requestid = @stats.requestid
729
+
624
730
  message.discovered_hosts = hosts.clone.compact
625
731
 
626
732
  @client.req(message) do |resp|
627
733
  respcount += 1
628
734
 
629
735
  if block_given?
630
- process_results_with_block(action, resp, block)
736
+ aggregate = process_results_with_block(action, resp, block, aggregate)
631
737
  else
632
738
  @stdout.print twirl.twirl(respcount, discovered.size) if @progress
633
739
 
634
- result << process_results_without_block(resp, action)
740
+ result, aggregate = process_results_without_block(resp, action, aggregate)
741
+
742
+ results << result
635
743
  end
636
744
  end
637
745
 
@@ -643,6 +751,8 @@ module MCollective
643
751
 
644
752
  sleep sleep_time unless last_batch
645
753
  end
754
+
755
+ @stats.aggregate_summary = aggregate.summarize if aggregate
646
756
  else
647
757
  @stderr.print("\nNo request sent, we did not discover any nodes.")
648
758
  end
@@ -656,7 +766,7 @@ module MCollective
656
766
  if block_given?
657
767
  return stats
658
768
  else
659
- return [result].flatten
769
+ return [results].flatten
660
770
  end
661
771
  end
662
772
 
@@ -693,30 +803,36 @@ module MCollective
693
803
 
694
804
  message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => opts[:filter], :options => opts})
695
805
  message.discovered_hosts = discovered.clone
696
- message.type = :direct_request if @force_direct_request
697
806
 
698
- result = []
807
+ results = []
699
808
  respcount = 0
700
809
 
701
810
  if discovered.size > 0
811
+ message.type = :direct_request if @force_direct_request
812
+
702
813
  if @progress && !block_given?
703
814
  twirl = Progress.new
704
815
  @stdout.puts
705
816
  @stdout.print twirl.twirl(respcount, discovered.size)
706
817
  end
707
818
 
819
+ aggregate = load_aggregate_functions(action, @ddl)
820
+
708
821
  @client.req(message) do |resp|
709
822
  respcount += 1
710
823
 
711
824
  if block_given?
712
- process_results_with_block(action, resp, block)
825
+ aggregate = process_results_with_block(action, resp, block, aggregate)
713
826
  else
714
827
  @stdout.print twirl.twirl(respcount, discovered.size) if @progress
715
828
 
716
- result << process_results_without_block(resp, action)
829
+ result, aggregate = process_results_without_block(resp, action, aggregate)
830
+
831
+ results << result
717
832
  end
718
833
  end
719
834
 
835
+ @stats.aggregate_summary = aggregate.summarize if aggregate
720
836
  @stats.client_stats = @client.stats
721
837
  else
722
838
  @stderr.print("\nNo request sent, we did not discover any nodes.")
@@ -731,62 +847,68 @@ module MCollective
731
847
  if block_given?
732
848
  return stats
733
849
  else
734
- return [result].flatten
850
+ return [results].flatten
735
851
  end
736
852
  end
737
853
 
738
854
  # Handles result sets that has no block associated, sets fails and ok
739
855
  # in the stats object and return a hash of the response to send to the
740
856
  # caller
741
- def process_results_without_block(resp, action)
857
+ def process_results_without_block(resp, action, aggregate)
742
858
  @stats.node_responded(resp[:senderid])
743
859
 
860
+ result = rpc_result_from_reply(@agent, action, resp)
861
+ aggregate = aggregate_reply(result, aggregate) if aggregate
862
+
744
863
  if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1
745
864
  @stats.ok if resp[:body][:statuscode] == 0
746
865
  @stats.fail if resp[:body][:statuscode] == 1
747
-
748
- return Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode],
749
- :statusmsg => resp[:body][:statusmsg], :data => resp[:body][:data]})
750
866
  else
751
867
  @stats.fail
752
-
753
- return Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode],
754
- :statusmsg => resp[:body][:statusmsg], :data => nil})
755
868
  end
869
+
870
+ [result, aggregate]
756
871
  end
757
872
 
758
873
  # process client requests by calling a block on each result
759
874
  # in this mode we do not do anything fancy with the result
760
875
  # objects and we raise exceptions if there are problems with
761
876
  # the data
762
- def process_results_with_block(action, resp, block)
877
+ def process_results_with_block(action, resp, block, aggregate)
763
878
  @stats.node_responded(resp[:senderid])
764
879
 
880
+ result = rpc_result_from_reply(@agent, action, resp)
881
+ aggregate = aggregate_reply(result, aggregate) if aggregate
882
+
765
883
  if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1
884
+ @stats.ok if resp[:body][:statuscode] == 0
885
+ @stats.fail if resp[:body][:statuscode] == 1
766
886
  @stats.time_block_execution :start
767
887
 
768
888
  case block.arity
769
- when 1
770
- block.call(resp)
771
- when 2
772
- rpcresp = Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode],
773
- :statusmsg => resp[:body][:statusmsg], :data => resp[:body][:data]})
774
- block.call(resp, rpcresp)
889
+ when 1
890
+ block.call(resp)
891
+ when 2
892
+ block.call(resp, result)
775
893
  end
776
894
 
777
895
  @stats.time_block_execution :end
778
896
  else
897
+ @stats.fail
898
+
779
899
  case resp[:body][:statuscode]
780
- when 2
781
- raise UnknownRPCAction, resp[:body][:statusmsg]
782
- when 3
783
- raise MissingRPCData, resp[:body][:statusmsg]
784
- when 4
785
- raise InvalidRPCData, resp[:body][:statusmsg]
786
- when 5
787
- raise UnknownRPCError, resp[:body][:statusmsg]
900
+ when 2
901
+ raise UnknownRPCAction, resp[:body][:statusmsg]
902
+ when 3
903
+ raise MissingRPCData, resp[:body][:statusmsg]
904
+ when 4
905
+ raise InvalidRPCData, resp[:body][:statusmsg]
906
+ when 5
907
+ raise UnknownRPCError, resp[:body][:statusmsg]
788
908
  end
789
909
  end
910
+
911
+ return aggregate
790
912
  end
791
913
  end
792
914
  end