X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Frpc%2Fhelpers.rb;fp=lib%2Fmcollective%2Frpc%2Fhelpers.rb;h=792736f957891cdcaad14fc34b5746bd597e6b02;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/rpc/helpers.rb b/lib/mcollective/rpc/helpers.rb new file mode 100644 index 0000000..792736f --- /dev/null +++ b/lib/mcollective/rpc/helpers.rb @@ -0,0 +1,306 @@ +module MCollective + module RPC + # Various utilities for the RPC system + class Helpers + # Parse JSON output as produced by printrpc and extract + # the "sender" of each rpc response + # + # The simplist valid JSON based data would be: + # + # [ + # {"sender" => "example.com"}, + # {"sender" => "another.com"} + # ] + def self.extract_hosts_from_json(json) + hosts = JSON.parse(json) + + raise "JSON hosts list is not an array" unless hosts.is_a?(Array) + + hosts.map do |host| + raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash) + raise "JSON host list does not have senders in it" unless host.include?("sender") + + host["sender"] + end.uniq + end + + # Given an array of something, make sure each is a string + # chomp off any new lines and return just the array of hosts + def self.extract_hosts_from_array(hosts) + [hosts].flatten.map do |host| + raise "#{host} should be a string" unless host.is_a?(String) + host.chomp + end + end + + # Returns a blob of text representing the results in a standard way + # + # It tries hard to do sane things so you often + # should not need to write your own display functions + # + # If the agent you are getting results for has a DDL + # it will use the hints in there to do the right thing specifically + # it will look at the values of display in the DDL to choose + # when to show results + # + # If you do not have a DDL you can pass these flags: + # + # printrpc exim.mailq, :flatten => true + # printrpc exim.mailq, :verbose => true + # + # If you've asked it to flatten the result it will not print sender + # hostnames, it will just print the result as if it's one huge result, + # handy for things like showing a combined mailq. + def self.rpcresults(result, flags = {}) + flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags) + + result_text = "" + ddl = nil + + # if running in verbose mode, just use the old style print + # no need for all the DDL helpers obfuscating the result + if flags[:format] == :json + if STDOUT.tty? + result_text = JSON.pretty_generate(result) + else + result_text = result.to_json + end + else + if flags[:verbose] + result_text = old_rpcresults(result, flags) + else + [result].flatten.each do |r| + begin + ddl ||= DDL.new(r.agent).action_interface(r.action.to_s) + + sender = r[:sender] + status = r[:statuscode] + message = r[:statusmsg] + result = r[:data] + + if flags[:force_display_mode] + display = flags[:force_display_mode] + else + display = ddl[:display] + end + + # appand the results only according to what the DDL says + case display + when :ok + if status == 0 + result_text << text_for_result(sender, status, message, result, ddl) + end + + when :failed + if status > 0 + result_text << text_for_result(sender, status, message, result, ddl) + end + + when :always + result_text << text_for_result(sender, status, message, result, ddl) + + when :flatten + result_text << text_for_flattened_result(status, result) + + end + rescue Exception => e + # no DDL so just do the old style print unchanged for + # backward compat + result_text = old_rpcresults(result, flags) + end + end + end + end + + result_text + end + + # Return text representing a result + def self.text_for_result(sender, status, msg, result, ddl) + statusses = ["", + Util.colorize(:red, "Request Aborted"), + Util.colorize(:yellow, "Unknown Action"), + Util.colorize(:yellow, "Missing Request Data"), + Util.colorize(:yellow, "Invalid Request Data"), + Util.colorize(:red, "Unknown Request Status")] + + result_text = "%-40s %s\n" % [sender, statusses[status]] + result_text << " %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK" + + # only print good data, ignore data that results from failure + if status == 0 + if result.is_a?(Hash) + # figure out the lengths of the display as strings, we'll use + # it later to correctly justify the output + lengths = result.keys.map do |k| + begin + ddl[:output][k][:display_as].size + rescue + k.to_s.size + end + end + + result.keys.sort_by{|k| k}.each do |k| + # get all the output fields nicely lined up with a + # 3 space front padding + begin + display_as = ddl[:output][k][:display_as] + rescue + display_as = k.to_s + end + + display_length = display_as.size + padding = lengths.max - display_length + 3 + result_text << " " * padding + + result_text << "#{display_as}:" + + if [String, Numeric].include?(result[k].class) + lines = result[k].to_s.split("\n") + + if lines.empty? + result_text << "\n" + else + lines.each_with_index do |line, i| + i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2) + + result_text << "#{padtxt}#{line}\n" + end + end + else + padding = " " * (lengths.max + 5) + result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n" + end + end + elsif status == 1 + # for status 1 we dont want to show half baked + # data by default since the DDL will supply all the defaults + # it just doesnt look right + else + result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t") + end + end + + result_text << "\n" + result_text + end + + # Returns text representing a flattened result of only good data + def self.text_for_flattened_result(status, result) + result_text = "" + + if status <= 1 + unless result.is_a?(String) + result_text << result.pretty_inspect + else + result_text << result + end + end + end + + # Backward compatible display block for results without a DDL + def self.old_rpcresults(result, flags = {}) + result_text = "" + + if flags[:flatten] + result.each do |r| + if r[:statuscode] <= 1 + data = r[:data] + + unless data.is_a?(String) + result_text << data.pretty_inspect + else + result_text << data + end + else + result_text << r.pretty_inspect + end + end + + result_text << "" + else + [result].flatten.each do |r| + + if flags[:verbose] + result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]] + + if r[:statuscode] <= 1 + r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"} + result_text << "\n\n" + elsif r[:statuscode] == 2 + # dont print anything, no useful data to display + # past what was already shown + elsif r[:statuscode] == 3 + # dont print anything, no useful data to display + # past what was already shown + elsif r[:statuscode] == 4 + # dont print anything, no useful data to display + # past what was already shown + else + result_text << " #{r[:statusmsg]}" + end + else + unless r[:statuscode] == 0 + result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])] + end + end + end + end + + result_text << "" + end + + # Add SimpleRPC common options + def self.add_simplerpc_options(parser, options) + parser.separator "" + parser.separator "RPC Options" + + # add SimpleRPC specific options to all clients that use our library + parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v| + options[:progress_bar] = false + end + + parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v| + options[:mcollective_limit_targets] = 1 + end + + parser.on('--batch SIZE', Integer, 'Do requests in batches') do |v| + options[:batch_size] = v + end + + parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v| + options[:batch_sleep_time] = v + end + + parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v| + options[:limit_seed] = v + end + + parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v| + raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/ + + if v =~ /^\d+$/ + options[:mcollective_limit_targets] = v.to_i + else + options[:mcollective_limit_targets] = v + end + end + + parser.on('--json', '-j', 'Produce JSON output') do |v| + options[:progress_bar] = false + options[:output_format] = :json + end + + parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v| + if v == "all" + options[:force_display_mode] = :always + else + options[:force_display_mode] = v.intern + end + + raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode]) + end + end + end + end +end