1 class MCollective::Application::Inventory<MCollective::Application
2 description "General reporting tool for nodes, collectives and subcollectives"
5 :description => "Script to run",
6 :arguments => ["--script SCRIPT"]
9 :description => "List all known collectives",
10 :arguments => ["--list-collectives", "--lc"],
14 option :collectivemap,
15 :description => "Create a DOT graph of all collectives",
16 :arguments => ["--collective-graph MAP", "--cg MAP", "--map MAP"]
18 def post_option_parser(configuration)
19 configuration[:node] = ARGV.shift if ARGV.size > 0
22 def validate_configuration(configuration)
23 unless configuration[:node] || configuration[:script] || configuration[:collectives] || configuration[:collectivemap]
24 raise "Need to specify either a node name, script to run or other options"
28 # Get all the known collectives and nodes that belong to them
30 util = rpcclient("rpcutil")
37 util.collective_info do |r, cinfo|
39 if cinfo[:data] && cinfo[:data][:collectives]
40 cinfo[:data][:collectives].each do |collective|
41 collectives[collective] ||= []
42 collectives[collective] << cinfo[:sender]
51 {:collectives => collectives, :nodes => nodes, :total_nodes => total}
54 # Writes a crude DOT graph to a file
55 def collectives_map(file)
56 File.open(file, "w") do |graph|
57 puts "Retrieving collective info...."
58 collectives = get_collectives
62 collectives[:collectives].keys.sort.each do |collective|
63 graph.puts ' subgraph "%s" {' % [ collective ]
65 collectives[:collectives][collective].each do |member|
66 graph.puts ' "%s" -- "%s"' % [ member, collective ]
74 puts "Graph of #{collectives[:total_nodes]} nodes has been written to #{file}"
78 # Prints a report of all known sub collectives
79 def collectives_report
80 collectives = get_collectives
82 puts " %-30s %s" % [ "Collective", "Nodes" ]
83 puts " %-30s %s" % [ "==========", "=====" ]
85 collectives[:collectives].sort_by {|key,count| count.size}.each do |collective|
86 puts " %-30s %d" % [ collective[0], collective[1].size ]
90 puts " %30s %d" % [ "Total nodes:", collectives[:nodes] ]
95 node = configuration[:node]
97 util = rpcclient("rpcutil")
98 util.identity_filter node
101 nodestats = util.custom_request("daemon_stats", {}, node, {"identity" => node}).first
104 STDERR.puts "Did not receive any results from node #{node}"
108 unless nodestats[:statuscode] == 0
109 STDERR.puts "Failed to retrieve daemon_stats from #{node}: #{nodestats[:statusmsg]}"
111 util.custom_request("inventory", {}, node, {"identity" => node}).each do |resp|
112 unless resp[:statuscode] == 0
113 STDERR.puts "Failed to retrieve inventory for #{node}: #{resp[:statusmsg]}"
120 puts "Inventory for #{resp[:sender]}:"
123 nodestats = nodestats[:data]
125 puts " Server Statistics:"
126 puts " Version: #{nodestats[:version]}"
127 puts " Start Time: #{Time.at(nodestats[:starttime])}"
128 puts " Config File: #{nodestats[:configfile]}"
129 puts " Collectives: #{data[:collectives].join(', ')}" if data.include?(:collectives)
130 puts " Main Collective: #{data[:main_collective]}" if data.include?(:main_collective)
131 puts " Process ID: #{nodestats[:pid]}"
132 puts " Total Messages: #{nodestats[:total]}"
133 puts " Messages Passed Filters: #{nodestats[:passed]}"
134 puts " Messages Filtered: #{nodestats[:filtered]}"
135 puts " Expired Messages: #{nodestats[:ttlexpired]}"
136 puts " Replies Sent: #{nodestats[:replies]}"
137 puts " Total Processor Time: #{nodestats[:times][:utime]} seconds"
138 puts " System Time: #{nodestats[:times][:stime]} seconds"
143 if data[:agents].size > 0
144 data[:agents].sort.in_groups_of(3, "") do |agents|
145 puts " %-15s %-15s %-15s" % agents
148 puts " No agents installed"
153 puts " Data Plugins:"
154 if data[:data_plugins].size > 0
155 data[:data_plugins].sort.in_groups_of(3, "") do |plugins|
156 puts " %-15s %-15s %-15s" % plugins.map{|p| p.gsub("_data", "")}
159 puts " No data plugins installed"
164 puts " Configuration Management Classes:"
165 if data[:classes].size > 0
166 data[:classes].sort.in_groups_of(2, "") do |klasses|
167 puts " %-30s %-30s" % klasses
170 puts " No classes applied"
176 if data[:facts].size > 0
177 data[:facts].sort_by{|f| f[0]}.each do |f|
178 puts " #{f[0]} => #{f[1]}"
181 puts " No facts known"
185 rescue Exception => e
186 STDERR.puts "Failed to display node inventory: #{e.class}: #{e}"
194 # Helpers to create a simple DSL for scriptlets
223 def page_heading(fmt)
231 # Expects a simple printf style format and apply it to
235 # format "%s:\t\t%s\t\t%s"
237 # fields { [ identity, facts["serialnumber"], facts["productname"] ] }
240 raise "Need to give a block to inventory" unless block_given?
242 blk.call if block_given?
244 raise "Need to define a format" if @fmt.nil?
245 raise "Need to define inventory fields" if @flds.nil?
247 util = rpcclient("rpcutil")
248 util.progress = false
250 util.inventory do |t, resp|
251 @node = {:identity => resp[:sender],
252 :facts => resp[:data][:facts],
253 :classes => resp[:data][:classes],
254 :agents => resp[:data][:agents]}
256 puts @fmt % @flds.call
260 # Use the ruby formatr gem to build reports using Perls formats
262 # It is kind of ugly but brings a lot of flexibility in report
263 # writing without building an entire reporting language.
265 # You need to have formatr installed to enable reports like:
267 # formatted_inventory do
272 # Node Report @<<<<<<<<<<<<<<<<<<<<<<<<<
275 # Hostname: Customer: Distribution:
276 # -------------------------------------------------------------------------
281 # @<<<<<<<<<<<<<<<< @<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
282 # identity, facts["customer"], facts["lsbdistdescription"]
283 # @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
284 # facts["processor0"]
287 def formatted_inventory(&blk)
290 raise "Need to give a block to formatted_inventory" unless block_given?
292 blk.call if block_given?
294 raise "Need to define page body format" if @page_body.nil?
296 body_fmt = FormatR::Format.new(@page_heading, @page_body)
297 body_fmt.setPageLength(@page_length)
300 util = rpcclient("rpcutil")
301 util.progress = false
303 util.inventory do |t, resp|
304 @node = {:identity => resp[:sender],
305 :facts => resp[:data][:facts],
306 :classes => resp[:data][:classes],
307 :agents => resp[:data][:agents]}
309 body_fmt.printFormat(binding)
311 rescue Exception => e
312 STDERR.puts "Could not create report: #{e.class}: #{e}"
323 if configuration[:script]
324 if File.exist?(configuration[:script])
325 eval(File.read(configuration[:script]))
327 raise "Could not find script to run: #{configuration[:script]}"
330 elsif configuration[:collectivemap]
331 collectives_map(configuration[:collectivemap])
333 elsif configuration[:collectives]