X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=plugins%2Fmcollective%2Fapplication%2Finventory.rb;fp=plugins%2Fmcollective%2Fapplication%2Finventory.rb;h=010bc68f4a5ff67d56e77e2b788538f5c6cb615a;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/plugins/mcollective/application/inventory.rb b/plugins/mcollective/application/inventory.rb new file mode 100644 index 0000000..010bc68 --- /dev/null +++ b/plugins/mcollective/application/inventory.rb @@ -0,0 +1,340 @@ +class MCollective::Application::Inventory "Script to run", + :arguments => ["--script SCRIPT"] + + option :collectives, + :description => "List all known collectives", + :arguments => ["--list-collectives", "--lc"], + :default => false, + :type => :bool + + option :collectivemap, + :description => "Create a DOT graph of all collectives", + :arguments => ["--collective-graph MAP", "--cg MAP", "--map MAP"] + + def post_option_parser(configuration) + configuration[:node] = ARGV.shift if ARGV.size > 0 + end + + def validate_configuration(configuration) + unless configuration[:node] || configuration[:script] || configuration[:collectives] || configuration[:collectivemap] + raise "Need to specify either a node name, script to run or other options" + end + end + + # Get all the known collectives and nodes that belong to them + def get_collectives + util = rpcclient("rpcutil") + util.progress = false + + collectives = {} + nodes = 0 + total = 0 + + util.collective_info do |r, cinfo| + begin + if cinfo[:data] && cinfo[:data][:collectives] + cinfo[:data][:collectives].each do |collective| + collectives[collective] ||= [] + collectives[collective] << cinfo[:sender] + end + + nodes += 1 + total += 1 + end + end + end + + {:collectives => collectives, :nodes => nodes, :total_nodes => total} + end + + # Writes a crude DOT graph to a file + def collectives_map(file) + File.open(file, "w") do |graph| + puts "Retrieving collective info...." + collectives = get_collectives + + graph.puts 'graph {' + + collectives[:collectives].keys.sort.each do |collective| + graph.puts ' subgraph "%s" {' % [ collective ] + + collectives[:collectives][collective].each do |member| + graph.puts ' "%s" -- "%s"' % [ member, collective ] + end + + graph.puts ' }' + end + + graph.puts '}' + + puts "Graph of #{collectives[:total_nodes]} nodes has been written to #{file}" + end + end + + # Prints a report of all known sub collectives + def collectives_report + collectives = get_collectives + + puts " %-30s %s" % [ "Collective", "Nodes" ] + puts " %-30s %s" % [ "==========", "=====" ] + + collectives[:collectives].sort_by {|key,count| count.size}.each do |collective| + puts " %-30s %d" % [ collective[0], collective[1].size ] + end + + puts + puts " %30s %d" % [ "Total nodes:", collectives[:nodes] ] + puts + end + + def node_inventory + node = configuration[:node] + + util = rpcclient("rpcutil") + util.identity_filter node + util.progress = false + + nodestats = util.custom_request("daemon_stats", {}, node, {"identity" => node}).first + + unless nodestats + STDERR.puts "Did not receive any results from node #{node}" + exit 1 + end + + unless nodestats[:statuscode] == 0 + STDERR.puts "Failed to retrieve daemon_stats from #{node}: #{nodestats[:statusmsg]}" + else + util.custom_request("inventory", {}, node, {"identity" => node}).each do |resp| + unless resp[:statuscode] == 0 + STDERR.puts "Failed to retrieve inventory for #{node}: #{resp[:statusmsg]}" + next + end + + data = resp[:data] + + begin + puts "Inventory for #{resp[:sender]}:" + puts + + nodestats = nodestats[:data] + + puts " Server Statistics:" + puts " Version: #{nodestats[:version]}" + puts " Start Time: #{Time.at(nodestats[:starttime])}" + puts " Config File: #{nodestats[:configfile]}" + puts " Collectives: #{data[:collectives].join(', ')}" if data.include?(:collectives) + puts " Main Collective: #{data[:main_collective]}" if data.include?(:main_collective) + puts " Process ID: #{nodestats[:pid]}" + puts " Total Messages: #{nodestats[:total]}" + puts " Messages Passed Filters: #{nodestats[:passed]}" + puts " Messages Filtered: #{nodestats[:filtered]}" + puts " Expired Messages: #{nodestats[:ttlexpired]}" + puts " Replies Sent: #{nodestats[:replies]}" + puts " Total Processor Time: #{nodestats[:times][:utime]} seconds" + puts " System Time: #{nodestats[:times][:stime]} seconds" + + puts + + puts " Agents:" + if data[:agents].size > 0 + data[:agents].sort.in_groups_of(3, "") do |agents| + puts " %-15s %-15s %-15s" % agents + end + else + puts " No agents installed" + end + + puts + + puts " Data Plugins:" + if data[:data_plugins].size > 0 + data[:data_plugins].sort.in_groups_of(3, "") do |plugins| + puts " %-15s %-15s %-15s" % plugins.map{|p| p.gsub("_data", "")} + end + else + puts " No data plugins installed" + end + + puts + + puts " Configuration Management Classes:" + if data[:classes].size > 0 + data[:classes].sort.in_groups_of(2, "") do |klasses| + puts " %-30s %-30s" % klasses + end + else + puts " No classes applied" + end + + puts + + puts " Facts:" + if data[:facts].size > 0 + data[:facts].sort_by{|f| f[0]}.each do |f| + puts " #{f[0]} => #{f[1]}" + end + else + puts " No facts known" + end + + break + rescue Exception => e + STDERR.puts "Failed to display node inventory: #{e.class}: #{e}" + end + end + end + + halt util.stats + end + + # Helpers to create a simple DSL for scriptlets + def format(fmt) + @fmt = fmt + end + + def fields(&blk) + @flds = blk + end + + def identity + @node[:identity] + end + + def facts + @node[:facts] + end + + def classes + @node[:classes] + end + + def agents + @node[:agents] + end + + def page_length(len) + @page_length = len + end + + def page_heading(fmt) + @page_heading = fmt + end + + def page_body(fmt) + @page_body = fmt + end + + # Expects a simple printf style format and apply it to + # each node: + # + # inventory do + # format "%s:\t\t%s\t\t%s" + # + # fields { [ identity, facts["serialnumber"], facts["productname"] ] } + # end + def inventory(&blk) + raise "Need to give a block to inventory" unless block_given? + + blk.call if block_given? + + raise "Need to define a format" if @fmt.nil? + raise "Need to define inventory fields" if @flds.nil? + + util = rpcclient("rpcutil") + util.progress = false + + util.inventory do |t, resp| + @node = {:identity => resp[:sender], + :facts => resp[:data][:facts], + :classes => resp[:data][:classes], + :agents => resp[:data][:agents]} + + puts @fmt % @flds.call + end + end + + # Use the ruby formatr gem to build reports using Perls formats + # + # It is kind of ugly but brings a lot of flexibility in report + # writing without building an entire reporting language. + # + # You need to have formatr installed to enable reports like: + # + # formatted_inventory do + # page_length 20 + # + # page_heading < resp[:sender], + :facts => resp[:data][:facts], + :classes => resp[:data][:classes], + :agents => resp[:data][:agents]} + + body_fmt.printFormat(binding) + end + rescue Exception => e + STDERR.puts "Could not create report: #{e.class}: #{e}" + exit 1 + end + + @fmt = nil + @flds = nil + @page_heading = nil + @page_body = nil + @page_length = 40 + + def main + if configuration[:script] + if File.exist?(configuration[:script]) + eval(File.read(configuration[:script])) + else + raise "Could not find script to run: #{configuration[:script]}" + end + + elsif configuration[:collectivemap] + collectives_map(configuration[:collectivemap]) + + elsif configuration[:collectives] + collectives_report + + else + node_inventory + end + end +end