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.
- data/lib/mcollective.rb +32 -23
- data/lib/mcollective/agent.rb +5 -0
- data/lib/mcollective/agents.rb +5 -16
- data/lib/mcollective/aggregate.rb +61 -0
- data/lib/mcollective/aggregate/base.rb +40 -0
- data/lib/mcollective/aggregate/result.rb +9 -0
- data/lib/mcollective/aggregate/result/base.rb +25 -0
- data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
- data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
- data/lib/mcollective/application.rb +7 -4
- data/lib/mcollective/applications.rb +3 -14
- data/lib/mcollective/cache.rb +145 -0
- data/lib/mcollective/client.rb +10 -87
- data/lib/mcollective/config.rb +22 -8
- data/lib/mcollective/data.rb +87 -0
- data/lib/mcollective/data/base.rb +67 -0
- data/lib/mcollective/data/result.rb +40 -0
- data/lib/mcollective/ddl.rb +113 -0
- data/lib/mcollective/ddl/agentddl.rb +185 -0
- data/lib/mcollective/ddl/base.rb +220 -0
- data/lib/mcollective/ddl/dataddl.rb +56 -0
- data/lib/mcollective/ddl/discoveryddl.rb +52 -0
- data/lib/mcollective/ddl/validatorddl.rb +6 -0
- data/lib/mcollective/discovery.rb +143 -0
- data/lib/mcollective/generators.rb +7 -0
- data/lib/mcollective/generators/agent_generator.rb +51 -0
- data/lib/mcollective/generators/base.rb +46 -0
- data/lib/mcollective/generators/data_generator.rb +51 -0
- data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
- data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
- data/lib/mcollective/generators/templates/ddl.erb +8 -0
- data/lib/mcollective/generators/templates/plugin.erb +7 -0
- data/lib/mcollective/logger/console_logger.rb +15 -15
- data/lib/mcollective/matcher.rb +167 -0
- data/lib/mcollective/matcher/parser.rb +60 -25
- data/lib/mcollective/matcher/scanner.rb +156 -78
- data/lib/mcollective/message.rb +47 -6
- data/lib/mcollective/monkey_patches.rb +17 -0
- data/lib/mcollective/optionparser.rb +18 -1
- data/lib/mcollective/pluginmanager.rb +3 -3
- data/lib/mcollective/pluginpackager.rb +10 -3
- data/lib/mcollective/pluginpackager/agent_definition.rb +28 -20
- data/lib/mcollective/pluginpackager/standard_definition.rb +11 -9
- data/lib/mcollective/registration/base.rb +3 -1
- data/lib/mcollective/rpc.rb +18 -24
- data/lib/mcollective/rpc/agent.rb +37 -113
- data/lib/mcollective/rpc/client.rb +186 -64
- data/lib/mcollective/rpc/helpers.rb +42 -80
- data/lib/mcollective/rpc/progress.rb +3 -3
- data/lib/mcollective/rpc/reply.rb +37 -13
- data/lib/mcollective/rpc/request.rb +17 -6
- data/lib/mcollective/rpc/result.rb +9 -5
- data/lib/mcollective/rpc/stats.rb +71 -24
- data/lib/mcollective/security/base.rb +41 -34
- data/lib/mcollective/shell.rb +1 -1
- data/lib/mcollective/ssl.rb +34 -0
- data/lib/mcollective/util.rb +194 -23
- data/lib/mcollective/validator.rb +80 -0
- data/spec/fixtures/util/1.in +10 -0
- data/spec/fixtures/util/1.out +10 -0
- data/spec/fixtures/util/2.in +1 -0
- data/spec/fixtures/util/2.out +1 -0
- data/spec/fixtures/util/3.in +1 -0
- data/spec/fixtures/util/3.out +2 -0
- data/spec/fixtures/util/4.in +5 -0
- data/spec/fixtures/util/4.out +9 -0
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/agents_spec.rb +34 -19
- data/spec/unit/aggregate/base_spec.rb +57 -0
- data/spec/unit/aggregate/result/base_spec.rb +28 -0
- data/spec/unit/aggregate/result/collection_result_spec.rb +18 -0
- data/spec/unit/aggregate/result/numeric_result_spec.rb +22 -0
- data/spec/unit/aggregate_spec.rb +110 -0
- data/spec/unit/application_spec.rb +8 -3
- data/spec/unit/applications_spec.rb +2 -2
- data/spec/unit/cache_spec.rb +115 -0
- data/spec/unit/client_spec.rb +78 -0
- data/spec/unit/config_spec.rb +32 -34
- data/spec/unit/data/base_spec.rb +90 -0
- data/spec/unit/data/result_spec.rb +64 -0
- data/spec/unit/data_spec.rb +158 -0
- data/spec/unit/ddl/agentddl_spec.rb +217 -0
- data/spec/unit/{rpc/ddl_spec.rb → ddl/base_spec.rb} +238 -224
- data/spec/unit/ddl/dataddl_spec.rb +65 -0
- data/spec/unit/ddl/discoveryddl_spec.rb +58 -0
- data/spec/unit/ddl_spec.rb +84 -0
- data/spec/unit/discovery_spec.rb +196 -0
- data/spec/unit/facts/base_spec.rb +1 -1
- data/spec/unit/generators/agent_generator_spec.rb +72 -0
- data/spec/unit/generators/base_spec.rb +83 -0
- data/spec/unit/generators/data_generator_spec.rb +37 -0
- data/spec/unit/generators/snippets/agent_ddl +19 -0
- data/spec/unit/generators/snippets/data_ddl +20 -0
- data/spec/unit/logger/console_logger_spec.rb +76 -0
- data/spec/unit/logger/syslog_logger_spec.rb +2 -2
- data/spec/unit/matcher/parser_spec.rb +27 -10
- data/spec/unit/matcher/scanner_spec.rb +108 -5
- data/spec/unit/matcher_spec.rb +260 -0
- data/spec/unit/message_spec.rb +35 -13
- data/spec/unit/optionparser_spec.rb +2 -2
- data/spec/unit/pluginpackager/agent_definition_spec.rb +59 -42
- data/spec/unit/pluginpackager/standard_definition_spec.rb +10 -8
- data/spec/unit/pluginpackager_spec.rb +131 -0
- data/spec/unit/plugins/mcollective/aggregate/average_spec.rb +45 -0
- data/spec/unit/plugins/mcollective/aggregate/sum_spec.rb +31 -0
- data/spec/unit/plugins/mcollective/aggregate/summary_spec.rb +45 -0
- data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +1 -1
- data/spec/unit/plugins/mcollective/connector/rabbitmq_spec.rb +478 -0
- data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +2 -0
- data/spec/unit/plugins/mcollective/data/agent_data_spec.rb +43 -0
- data/spec/unit/plugins/mcollective/data/fstat_data_spec.rb +135 -0
- data/spec/unit/plugins/mcollective/discovery/flatfile_spec.rb +48 -0
- data/spec/unit/plugins/mcollective/discovery/mc_spec.rb +40 -0
- data/spec/unit/plugins/mcollective/packagers/debpackage_packager_spec.rb +41 -15
- data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +1 -1
- data/spec/unit/plugins/mcollective/packagers/rpmpackage_packager_spec.rb +22 -38
- data/spec/unit/plugins/mcollective/validator/array_validator_spec.rb +19 -0
- data/spec/unit/plugins/mcollective/validator/ipv4address_validator_spec.rb +19 -0
- data/spec/unit/plugins/mcollective/validator/ipv6address_validator_spec.rb +19 -0
- data/spec/unit/plugins/mcollective/validator/length_validator_spec.rb +19 -0
- data/spec/unit/plugins/mcollective/validator/regex_validator_spec.rb +19 -0
- data/spec/unit/plugins/mcollective/validator/shellsafe_validator_spec.rb +21 -0
- data/spec/unit/plugins/mcollective/validator/typecheck_validator_spec.rb +23 -0
- data/spec/unit/registration/base_spec.rb +1 -1
- data/spec/unit/rpc/actionrunner_spec.rb +2 -2
- data/spec/unit/rpc/agent_spec.rb +41 -65
- data/spec/unit/rpc/client_spec.rb +430 -134
- data/spec/unit/rpc/reply_spec.rb +31 -1
- data/spec/unit/rpc/request_spec.rb +33 -12
- data/spec/unit/rpc/result_spec.rb +7 -0
- data/spec/unit/rpc/stats_spec.rb +14 -14
- data/spec/unit/rpc_spec.rb +16 -0
- data/spec/unit/security/base_spec.rb +8 -8
- data/spec/unit/ssl_spec.rb +20 -2
- data/spec/unit/string_spec.rb +15 -0
- data/spec/unit/util_spec.rb +141 -21
- data/spec/unit/validator_spec.rb +67 -0
- metadata +145 -7
- 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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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
|
-
#
|
27
|
-
#
|
28
|
-
# end
|
19
|
+
# action "foo" do
|
20
|
+
# implemented_by "/some/script.sh"
|
29
21
|
# end
|
30
|
-
#
|
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 :
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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.
|
77
|
-
@reply = RPC.
|
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
|
-
|
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
|
-
|
337
|
-
|
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 :
|
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
|
-
@
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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.
|
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.
|
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"] <<
|
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
|
-
|
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
|
-
@
|
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,
|
502
|
+
@discovered_agents = @client.discover(@filter, discovery_timeout, @limit_targets)
|
457
503
|
else
|
458
|
-
@discovered_agents = @client.discover(@filter,
|
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
|
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))
|
601
|
+
pct = Integer((discover.size * (count.to_f / 100)))
|
548
602
|
pct == 0 ? count = 1 : count = pct
|
549
603
|
else
|
550
|
-
count = count
|
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(
|
562
|
-
result <<
|
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.
|
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
|
-
|
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
|
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 [
|
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
|
-
|
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
|
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 [
|
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
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
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
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
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
|