choria-mcorpc-support 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,86 @@
1
+ module MCollective
2
+ module RPC
3
+ # Simple class to manage compliant requests for MCollective::RPC agents
4
+ class Request
5
+ attr_accessor :time, :action, :data, :sender, :agent, :uniqid, :caller, :ddl
6
+
7
+ def initialize(msg, ddl)
8
+ @time = msg[:msgtime]
9
+ @action = msg[:body][:action] || msg[:body]["action"]
10
+ @data = msg[:body][:data] || msg[:body]["data"]
11
+ @sender = msg[:senderid]
12
+ @agent = msg[:body][:agent] || msg[:body]["agent"]
13
+ @uniqid = msg[:requestid]
14
+ @caller = msg[:callerid] || "unknown"
15
+ @ddl = ddl
16
+ end
17
+
18
+ # In a scenario where a request came from a JSON pure medium like a REST
19
+ # service or other language client DDL::AgentDDL#validate_rpc_request will
20
+ # check "package" against the intput :package should the input "package" not
21
+ # also be known
22
+ #
23
+ # Thus once the request is built it will also have "package" and not :package
24
+ # data, so we need to fetch the correct key out of the hash.
25
+ def compatible_key(key)
26
+ return key if data.include?(key)
27
+
28
+ if ddl
29
+ input = ddl.action_interface(action)[:input]
30
+
31
+ # if :package is requested and the DDL also declares "package" we cant tell it to fetch
32
+ # "package", hence the check against the input here
33
+ return key.to_s if key.is_a?(Symbol) && !input.include?(key.to_s) && data.include?(key.to_s)
34
+ end
35
+
36
+ key
37
+ end
38
+
39
+ # If data is a hash, quick helper to get access to it's include? method
40
+ # else returns false
41
+ def include?(key)
42
+ return false unless @data.is_a?(Hash)
43
+
44
+ @data.include?(compatible_key(key))
45
+ end
46
+
47
+ # If no :process_results is specified always respond else respond
48
+ # based on the supplied property
49
+ def should_respond?
50
+ return false unless @data.is_a?(Hash)
51
+ return @data[:process_results] if @data.include?(:process_results)
52
+ return @data["process_results"] if @data.include?("process_results")
53
+
54
+ true
55
+ end
56
+
57
+ # If data is a hash, gives easy access to its members, else returns nil
58
+ def [](key)
59
+ return nil unless @data.is_a?(Hash)
60
+ return @data[compatible_key(key)]
61
+ end
62
+
63
+ def fetch(key, default)
64
+ return nil unless @data.is_a?(Hash)
65
+ return @data.fetch(compatible_key(key), default)
66
+ end
67
+
68
+ def to_hash
69
+ {:agent => @agent,
70
+ :action => @action,
71
+ :data => @data}
72
+ end
73
+
74
+ # Validate the request against the DDL
75
+ def validate!
76
+ @ddl.validate_rpc_request(@action, @data)
77
+ end
78
+
79
+ def to_json
80
+ to_hash.merge!({:sender => @sender,
81
+ :callerid => @callerid,
82
+ :uniqid => @uniqid}).to_json
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,90 @@
1
+ module MCollective
2
+ module RPC
3
+ # Simple class to manage compliant results from MCollective::RPC agents
4
+ #
5
+ # Currently it just fakes Hash behaviour to the result to remain backward
6
+ # compatible but it also knows which agent and action produced it so you
7
+ # can associate results to a DDL
8
+ class Result
9
+ attr_reader :agent, :action, :results
10
+
11
+ include Enumerable
12
+
13
+ def initialize(agent, action, result={})
14
+ @agent = agent
15
+ @action = action
16
+ @results = result
17
+
18
+ convert_data_based_on_ddl if ddl
19
+ end
20
+
21
+ def ddl
22
+ @_ddl ||= DDL.new(agent)
23
+ rescue
24
+ nil
25
+ end
26
+
27
+ def data
28
+ @results[:data] = @results.delete("data") if @results.include?("data")
29
+
30
+ self[:data]
31
+ end
32
+
33
+ # Converts keys on the supplied data to those listed as outputs
34
+ # in the DDL. This is to facilitate JSON based transports
35
+ # without forcing everyone to rewrite DDLs and clients to
36
+ # convert symbols to strings, the data will be on symbol keys
37
+ # if the DDL has a symbol and not a string output defined
38
+ def convert_data_based_on_ddl
39
+ interface = ddl.action_interface(action)
40
+
41
+ return if interface.fetch(:output, {}).empty?
42
+
43
+ interface[:output].each do |output, properties|
44
+ next if data.include?(output)
45
+
46
+ if output.is_a?(Symbol) && data.include?(output.to_s)
47
+ data[output] = data.delete(output.to_s)
48
+ end
49
+ end
50
+ end
51
+
52
+ def compatible_key(key)
53
+ if key.is_a?(Symbol) && @results.include?(key.to_s)
54
+ key.to_s
55
+ else
56
+ key
57
+ end
58
+ end
59
+
60
+ def [](key)
61
+ @results[compatible_key(key)]
62
+ end
63
+
64
+ def []=(key, item)
65
+ @results[key] = item
66
+ end
67
+
68
+ def fetch(key, default)
69
+ @results.fetch(compatible_key(key), default)
70
+ end
71
+
72
+ def each
73
+ @results.each_pair {|k,v| yield(k,v) }
74
+ end
75
+
76
+ def to_json(*a)
77
+ {:agent => @agent,
78
+ :action => @action,
79
+ :sender => self[:sender],
80
+ :statuscode => self[:statuscode],
81
+ :statusmsg => self[:statusmsg],
82
+ :data => data}.to_json(*a)
83
+ end
84
+
85
+ def <=>(other)
86
+ self[:sender] <=> other[:sender]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,294 @@
1
+ module MCollective
2
+ module RPC
3
+ # Class to wrap all the stats and to keep track of some timings
4
+ class Stats
5
+ attr_accessor :noresponsefrom, :unexpectedresponsefrom, :starttime, :discoverytime, :blocktime, :responses
6
+ attr_accessor :totaltime, :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom
7
+ attr_accessor :responsesfrom, :requestid, :aggregate_summary, :ddl, :aggregate_failures
8
+
9
+ def initialize
10
+ reset
11
+ end
12
+
13
+ # Resets stats, if discovery time is set we keep it as it was
14
+ def reset
15
+ @noresponsefrom = []
16
+ @unexpectedresponsefrom = []
17
+ @responsesfrom = []
18
+ @responses = 0
19
+ @starttime = Time.now.to_f
20
+ @discoverytime = 0 unless @discoverytime
21
+ @blocktime = 0
22
+ @totaltime = 0
23
+ @discovered = 0
24
+ @discovered_nodes = []
25
+ @okcount = 0
26
+ @failcount = 0
27
+ @requestid = nil
28
+ @aggregate_summary = []
29
+ @aggregate_failures = []
30
+ end
31
+
32
+ # returns a hash of our stats
33
+ def to_hash
34
+ {:noresponsefrom => @noresponsefrom,
35
+ :unexpectedresponsefrom => @unexpectedresponsefrom,
36
+ :starttime => @starttime,
37
+ :discoverytime => @discoverytime,
38
+ :blocktime => @blocktime,
39
+ :responses => @responses,
40
+ :totaltime => @totaltime,
41
+ :discovered => @discovered,
42
+ :discovered_nodes => @discovered_nodes,
43
+ :okcount => @okcount,
44
+ :requestid => @requestid,
45
+ :failcount => @failcount,
46
+ :aggregate_summary => @aggregate_summary,
47
+ :aggregate_failures => @aggregate_failures}
48
+ end
49
+
50
+ # Fake hash access to keep things backward compatible
51
+ def [](key)
52
+ to_hash[key]
53
+ rescue
54
+ nil
55
+ end
56
+
57
+ # increment the count of ok hosts
58
+ def ok
59
+ @okcount += 1
60
+ rescue
61
+ @okcount = 1
62
+ end
63
+
64
+ # increment the count of failed hosts
65
+ def fail
66
+ @failcount += 1
67
+ rescue
68
+ @failcount = 1
69
+ end
70
+
71
+ # Re-initializes the object with stats from the basic client
72
+ def client_stats=(stats)
73
+ @noresponsefrom = stats[:noresponsefrom]
74
+ @unexpectedresponsefrom = stats[:unexpectedresponsefrom]
75
+ @responses = stats[:responses]
76
+ @starttime = stats[:starttime]
77
+ @blocktime = stats[:blocktime]
78
+ @totaltime = stats[:totaltime]
79
+ @requestid = stats[:requestid]
80
+ @discoverytime = stats[:discoverytime] if @discoverytime == 0
81
+ end
82
+
83
+ # Utility to time discovery from :start to :end
84
+ def time_discovery(action)
85
+ if action == :start
86
+ @discovery_start = Time.now.to_f
87
+ elsif action == :end
88
+ @discoverytime = Time.now.to_f - @discovery_start
89
+ else
90
+ raise("Uknown discovery action #{action}")
91
+ end
92
+ rescue
93
+ @discoverytime = 0
94
+ end
95
+
96
+ # helper to time block execution time
97
+ def time_block_execution(action)
98
+ if action == :start
99
+ @block_start = Time.now.to_f
100
+ elsif action == :end
101
+ @blocktime += Time.now.to_f - @block_start
102
+ else
103
+ raise("Uknown block action #{action}")
104
+ end
105
+ rescue
106
+ @blocktime = 0
107
+ end
108
+
109
+ # Update discovered and discovered_nodes based on
110
+ # discovery results
111
+ def discovered_agents(agents)
112
+ @discovered_nodes = agents
113
+ @discovered = agents.size
114
+ end
115
+
116
+ # Helper to calculate total time etc
117
+ def finish_request
118
+ @totaltime = @blocktime + @discoverytime
119
+
120
+ # figure out who responded unexpectedly
121
+ @unexpectedresponsefrom = @responsesfrom - @discovered_nodes
122
+
123
+ # figure out who we had no responses from
124
+ @noresponsefrom = @discovered_nodes - @responsesfrom
125
+ rescue
126
+ @totaltime = 0
127
+ @noresponsefrom = []
128
+ @unexpectedresponsefrom = []
129
+ end
130
+
131
+ # Helper to keep track of who we received responses from
132
+ def node_responded(node)
133
+ @responsesfrom << node
134
+ rescue
135
+ @responsesfrom = [node]
136
+ end
137
+
138
+ def text_for_aggregates
139
+ result = StringIO.new
140
+
141
+ @aggregate_summary.each do |aggregate|
142
+ output_item = aggregate.result[:output]
143
+
144
+ begin
145
+ action_interface = @ddl.action_interface(aggregate.action)
146
+ display_as = action_interface[:output][output_item][:display_as]
147
+ rescue
148
+ display_as = output_item
149
+ end
150
+
151
+ if aggregate.is_a?(Aggregate::Result::Base)
152
+ aggregate_report = aggregate.to_s
153
+ else
154
+ next
155
+ end
156
+
157
+ result.puts Util.colorize(:bold, "Summary of %s:" % display_as)
158
+ result.puts
159
+ unless aggregate_report == ""
160
+ result.puts aggregate.to_s.split("\n").map{|x| " " + x}.join("\n")
161
+ else
162
+ result.puts Util.colorize(:yellow, " No aggregate summary could be computed")
163
+ end
164
+ result.puts
165
+ end
166
+
167
+ @aggregate_failures.each do |failed|
168
+ case(failed[:type])
169
+ when :startup
170
+ message = "exception raised while processing startup hook"
171
+ when :create
172
+ message = "unspecified output '#{failed[:name]}' for the action"
173
+ when :process_result
174
+ message = "exception raised while processing result data"
175
+ when :summarize
176
+ message = "exception raised while summarizing"
177
+ end
178
+
179
+ result.puts Util.colorize(:bold, "Summary of %s:" % failed[:name])
180
+ result.puts
181
+ result.puts Util.colorize(:yellow, " Could not compute summary - %s" % message)
182
+ result.puts
183
+ end
184
+
185
+ result.string
186
+ end
187
+
188
+ # Returns a blob of text representing the request status based on the
189
+ # stats contained in this class
190
+ def report(caption = "rpc stats", summarize = true, verbose = false)
191
+ result_text = []
192
+
193
+ if verbose
194
+ if @aggregate_summary.size > 0 && summarize
195
+ result_text << text_for_aggregates
196
+ else
197
+ result_text << ""
198
+ end
199
+
200
+ result_text << Util.colorize(:yellow, "---- #{caption} ----")
201
+
202
+ if @discovered
203
+ @responses < @discovered ? color = :red : color = :reset
204
+ result_text << " Nodes: %s / %s" % [ Util.colorize(color, @discovered), Util.colorize(color, @responses) ]
205
+ else
206
+ result_text << " Nodes: #{@responses}"
207
+ end
208
+
209
+ @failcount < 0 ? color = :red : color = :reset
210
+
211
+ result_text << " Pass / Fail: %s / %s" % [Util.colorize(color, @okcount), Util.colorize(color, @failcount) ]
212
+ result_text << " Start Time: %s" % [Time.at(@starttime)]
213
+ result_text << " Discovery Time: %.2fms" % [@discoverytime * 1000]
214
+ result_text << " Agent Time: %.2fms" % [@blocktime * 1000]
215
+ result_text << " Total Time: %.2fms" % [@totaltime * 1000]
216
+ else
217
+ if @discovered
218
+ @responses < @discovered ? color = :red : color = :green
219
+
220
+ if @aggregate_summary.size + @aggregate_failures.size > 0 && summarize
221
+ result_text << text_for_aggregates
222
+ else
223
+ result_text << ""
224
+ end
225
+
226
+ result_text << "Finished processing %s / %s hosts in %.2f ms" % [Util.colorize(color, @responses), Util.colorize(color, @discovered), @blocktime * 1000]
227
+ else
228
+ result_text << "Finished processing %s hosts in %.2f ms" % [Util.colorize(:bold, @responses), @blocktime * 1000]
229
+ end
230
+ end
231
+
232
+ no_response_r = no_response_report
233
+ unexpected_response_r = unexpected_response_report
234
+ if no_response_r || unexpected_response_r
235
+ result_text << ""
236
+ end
237
+
238
+ if no_response_r != ""
239
+ result_text << "" << no_response_r
240
+ end
241
+
242
+ if unexpected_response_r != ""
243
+ result_text << "" << unexpected_response_r
244
+ end
245
+
246
+ if no_response_r || unexpected_response_r
247
+ result_text << ""
248
+ end
249
+
250
+ result_text.join("\n")
251
+ end
252
+
253
+ # Returns a blob of text indicating what nodes did not respond
254
+ def no_response_report
255
+ result_text = StringIO.new
256
+
257
+ if @noresponsefrom.size > 0
258
+ result_text.puts Util.colorize(:red, "No response from:")
259
+ result_text.puts
260
+
261
+ field_size = Util.field_size(@noresponsefrom, 30)
262
+ fields_num = Util.field_number(field_size)
263
+ format = " " + ( " %-#{field_size}s" * fields_num )
264
+
265
+ @noresponsefrom.sort.in_groups_of(fields_num) do |c|
266
+ result_text.puts format % c
267
+ end
268
+ end
269
+
270
+ result_text.string
271
+ end
272
+
273
+ # Returns a blob of text indicating what nodes responded but weren't discovered
274
+ def unexpected_response_report
275
+ result_text = StringIO.new
276
+
277
+ if @unexpectedresponsefrom.size > 0
278
+ result_text.puts Util.colorize(:red, "Unexpected response from:")
279
+ result_text.puts
280
+
281
+ field_size = Util.field_size(@unexpectedresponsefrom, 30)
282
+ fields_num = Util.field_number(field_size)
283
+ format = " " + ( " %-#{field_size}s" * fields_num )
284
+
285
+ @unexpectedresponsefrom.sort.in_groups_of(fields_num) do |c|
286
+ result_text.puts format % c
287
+ end
288
+ end
289
+
290
+ result_text.string
291
+ end
292
+ end
293
+ end
294
+ end