]> git.openstreetmap.org Git - chef.git/blob - cookbooks/hardware/templates/default/ohai.rb.erb
24a844a7f20c564a61fddd0bfbbeda74dbb6f68f
[chef.git] / cookbooks / hardware / templates / default / ohai.rb.erb
1 require "pathname"
2
3 Ohai.plugin(:Hardware) do
4   provides "hardware"
5
6   def read_sysctl_link(file)
7     File.basename(File.readlink(file))
8   rescue Errno::ENOENT
9   end
10
11   def read_sysctl_file(file)
12     IO.read(file).strip
13   rescue Errno::ENOENT, Errno::EINVAL
14   end
15
16   def parse_memory_size(size)
17     if size =~ /^(\d+(?:\.\d+)?)\s*TB/i
18       Regexp.last_match(1).to_f * 2**30
19     elsif size =~ /^(\d+(?:\.\d+)?)\s*GB/i
20       Regexp.last_match(1).to_f * 2**20
21     elsif size =~ /^(\d+(?:\.\d+)?)\s*MB/i
22       Regexp.last_match(1).to_f * 2**10
23     end
24   end
25
26   def format_disk_size(kb)
27     if kb == 0
28       ""
29     else
30       kblog10 = Math.log10(kb).floor
31
32       kb = kb.to_f * 2 / 10**kblog10
33       kb = kb.round.to_f / 2
34
35       if kblog10 >= 9
36         format "%gTB", kb * 10**(kblog10 - 9)
37       elsif kblog10 >= 6
38         format "%dGB", kb * 10**(kblog10 - 6)
39       else
40         format "%dMB", kb * 10**(kblog10 - 3)
41       end
42     end
43   end
44
45   def memory_to_disk_size(size)
46     format_disk_size(parse_memory_size(size))
47   end
48
49   def find_sas_device(address)
50     file = Dir.glob("/sys/class/scsi_generic/sg*/device/sas_address").find do |file|
51       read_sysctl_file(file) == "0x#{address}"
52     end
53
54     if file
55       dir = File.dirname(file)
56       device = Dir.glob("#{dir}/block/*").first ||
57                Dir.glob("#{dir}/scsi_generic/*").first
58
59       "/dev/#{File.basename(device)}"
60     end
61   end
62
63   def pci_devices
64     device = nil
65
66     IO.popen(["lspci", "-Dkvmm"]).each_with_object(Mash.new) do |line, devices|
67       if line =~ /^Slot:\s+((\h{4}):(\h{2}):(\h{2}).(\h))\s*$/
68         device = {
69           :slot => Regexp.last_match(1),
70           :domain => Regexp.last_match(2),
71           :bus => Regexp.last_match(3),
72           :device => Regexp.last_match(4),
73           :function => Regexp.last_match(5)
74         }
75       elsif device && line =~ /^([A-Z]+):\s+(.*)\s*$/i
76         case Regexp.last_match(1)
77         when "Class" then device[:class_name] = Regexp.last_match(2)
78         when "Vendor" then device[:vendor_name] = Regexp.last_match(2)
79         when "Device" then device[:device_name] = Regexp.last_match(2)
80         when "SVendor" then device[:subsystem_vendor_name] = Regexp.last_match(2)
81         when "SDevice" then device[:subsystem_device_name] = Regexp.last_match(2)
82         when "PhySlot" then device[:physical_slot] = Regexp.last_match(2)
83         when "Rev" then device[:revision] = Regexp.last_match(2)
84         when "ProgIf" then device[:programming_interface] = Regexp.last_match(2)
85         when "Driver" then device[:driver] = Regexp.last_match(2)
86         when "Module" then device[:modules] = Array(device[:modules]) << Regexp.last_match(2)
87         end
88       elsif device && line =~ /^\s*$/
89         devices[device[:slot]] = device
90         device = nil
91       end
92     end
93   end
94
95   def network_devices
96     Dir.glob("/sys/class/net/*").each_with_object(Mash.new) do |device, devices|
97       name = File.basename(device)
98
99       devices[name] = {
100         :device => read_sysctl_link("#{device}/device"),
101         :duplex => read_sysctl_file("#{device}/duplex"),
102         :speed => read_sysctl_file("#{device}/speed")
103       }.delete_if { |_, v| v.nil? }
104     end
105   end
106
107   def memory_devices
108     device = nil
109
110     IO.popen(["dmidecode", "-t", "memory"]).each_with_object([]) do |line, devices|
111       if line =~ /^Memory Device\s*$/
112         device = {}
113       elsif device && line =~ /^\s+([A-Z ]+):\s+(.*)\s*$/i
114         device[Regexp.last_match(1).tr(" ", "_").downcase.to_sym] = Regexp.last_match(2).strip
115       elsif device && line =~ /^\s*$/
116         devices << device
117         device = nil
118       end
119     end
120   end
121
122   def disk_devices
123     disk = Mash.new
124
125     disk[:controllers] = []
126     disk[:arrays] = []
127     disk[:disks] = []
128
129     find_direct_disks(disk)
130     find_nvme_disks(disk)
131
132     find_hp_disks(disk) if File.exist?("/usr/sbin/hpssacli")
133     find_megaraid_disks(disk) if File.exist?("/usr/sbin/megacli")
134     find_mpt_disks(disk) if File.exist?("/usr/sbin/sas2ircu")
135     find_adaptec_disks(disk) if File.exist?("/usr/sbin/arcconf")
136     find_areca_disks(disk) if File.exist?("/opt/areca/x86_64/cli64")
137
138     find_md_arrays(disk)
139
140     disk[:disks].each do |disk|
141       if disk[:vendor] =~ /^CVPR/ && disk[:model] == "INTEL"
142         disk[:model] = disk[:serial_number]
143         disk[:serial_number] = disk[:vendor]
144         disk[:vendor] = "INTEL"
145       end
146
147       if disk[:vendor].nil? && disk[:model] =~ /^ATA\s+(.*)$/
148         disk[:vendor] = "ATA"
149         disk[:model] = Regexp.last_match(1)
150       end
151
152       if disk[:vendor].nil? || disk[:vendor] == "ATA"
153         if disk[:model] =~ /^(\S+)\s+(.*)$/
154           disk[:vendor] = Regexp.last_match(1)
155           disk[:model] = Regexp.last_match(2)
156         elsif disk[:model] =~ /^ST/
157           disk[:vendor] = "SEAGATE"
158         elsif disk[:model] =~ /^C300-(.*)$/
159           disk[:vendor] = "CRUCIAL"
160           disk[:model] = Regexp.last_match(1)
161         end
162       end
163
164       disk[:model].sub!(/-.*$/, "")
165     end
166
167     disk
168   end
169
170   def find_direct_disks(devices)
171     Dir.glob("/sys/class/scsi_host/host*") do |host|
172       driver = read_sysctl_file("#{host}/proc_name")
173
174       if driver == "ahci" || driver == "mptsas"
175         bus = host.sub("/sys/class/scsi_host/host", "")
176
177         Dir.glob("/sys/bus/scsi/devices/#{bus}:0:*").each do |device|
178           next unless File.exist?("#{device}/scsi_disk")
179
180           block = Dir.glob("#{device}/block/*").first
181           size = read_sysctl_file("#{block}/size").to_f / 2
182
183           devices[:disks] << {
184             :id => devices[:disks].count,
185             :device => "/dev/#{File.basename(block)}",
186             :vendor => read_sysctl_file("#{device}/vendor"),
187             :model => read_sysctl_file("#{device}/model"),
188             :firmware_version => read_sysctl_file("#{device}/rev"),
189             :size => format_disk_size(size),
190             :arrays => []
191           }
192         end
193       end
194     end
195   end
196
197   def find_nvme_disks(devices)
198     Dir.glob("/sys/class/misc/nvme*") do |device|
199       controller = {
200         :id => devices[:controllers].count,
201         :pci_slot => File.basename(Pathname.new("#{device}/device").realpath),
202         :arrays => [],
203         :disks => []
204       }
205
206       devices[:controllers] << controller
207
208       IO.popen(["lspci", "-Dkvmm", "-s", controller[:pci_slot]]).each do |line|
209         if line =~ /^SVendor:\s+(\S.*\S)\s*$/
210           controller[:vendor] = Regexp.last_match(1)
211         elsif line =~ /^SDevice:\s+(\S.*\S)\s*$/
212           controller[:model] = Regexp.last_match(1)
213         end
214       end
215
216       Dir.glob("#{device}/device/block/*").each do |block|
217         size = read_sysctl_file("#{block}/size").to_f / 2
218
219         disk = {
220           :id => devices[:disks].count,
221           :controller => controller[:id],
222           :device => "/dev/#{File.basename(block)}",
223           :vendor => controller[:vendor],
224           :model => controller[:model],
225           :size => format_disk_size(size),
226           :arrays => []
227         }
228
229         devices[:disks] << disk
230         controller[:disks] << disk[:id]
231       end
232     end
233   end
234
235   def find_md_arrays(devices)
236     array = nil
237
238     File.new("/proc/mdstat", "r").each do |line|
239       if line =~ /^(md\d+) : active raid(\d+)((?: (?:sd[a-z]|nvme\d+n\d+)\d*\[\d+\](?:\([A-Z]\))*)+)$/
240         array = {
241           :id => devices[:arrays].count,
242           :device => "/dev/#{Regexp.last_match(1)}",
243           :raid_level => Regexp.last_match(2),
244           :disks => []
245         }
246
247         Regexp.last_match(3).scan(/ (sd[a-z]+|nvme\d+n\d+)\d*\[\d+\](?:\([A-Z]\))*/).flatten.each do |device|
248           if disk = devices[:disks].find { |d| d[:device] == "/dev/#{device}" }
249             disk[:arrays] << array[:id]
250             array[:disks] << disk[:id]
251           end
252         end
253
254         devices[:arrays] << array
255       elsif array && line =~ /^\s+(\d+) blocks/
256         array[:size] = format_disk_size(Regexp.last_match(1).to_i)
257       end
258     end
259   end
260
261   def find_hp_disks(devices)
262     controllers = []
263     disks = []
264
265     controller = nil
266     array = nil
267     disk = nil
268
269     IO.popen(%w(hpssacli controller all show config detail)).each do |line|
270       if line =~ /^Smart Array (\S+) /
271         controller = {
272           :id => devices[:controllers].count,
273           :model => Regexp.last_match(1),
274           :arrays => [],
275           :disks => []
276         }
277
278         devices[:controllers] << controller
279
280         controllers << controller
281
282         array = nil
283         disk = nil
284       elsif controller && line =~ /^   (\S.*):\s+(.*)$/
285         case Regexp.last_match(1)
286         when "Serial Number" then controller[:serial_number] = Regexp.last_match(2)
287         when "Hardware Revision" then controller[:hardware_version] = Regexp.last_match(2)
288         when "Firmware Version" then controller[:firmware_version] = Regexp.last_match(2)
289         when "PCI Address (Domain:Bus:Device.Function)" then controller[:pci_slot] = Regexp.last_match(2)
290         end
291       elsif controller && line =~ /^      Logical Drive: (\d+)$/
292         array = {
293           :id => devices[:arrays].count,
294           :controller => controller[:id],
295           :number => Regexp.last_match(1),
296           :disks => []
297         }
298
299         devices[:arrays] << array
300         controller[:arrays] << array[:id]
301
302         disk = nil
303       elsif controller && line =~ /^      physicaldrive (\S+) /
304         disks << Regexp.last_match(1)
305       elsif array && line =~ /^      physicaldrive (\S+)$/
306         disk = {
307           :id => devices[:disks].count,
308           :controller => controller[:id],
309           :arrays => [array[:id]],
310           :location => Regexp.last_match(1),
311           :smart_device => "cciss,#{disks.find_index(Regexp.last_match(1))}"
312         }
313
314         devices[:disks] << disk
315         controller[:disks] << disk[:id]
316         array[:disks] << disk[:id]
317       elsif disk && line =~ /^         (\S[^:]+):\s+(.*\S)\s*$/
318         case Regexp.last_match(1)
319         when "Interface Type" then disk[:interface] = Regexp.last_match(2)
320         when "Size" then disk[:size] = Regexp.last_match(2)
321         when "Rotational Speed" then disk[:rpm] = Regexp.last_match(2)
322         when "Firmware Revision" then disk[:firmware_version] = Regexp.last_match(2)
323         when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
324         when "Model" then disk[:model] = Regexp.last_match(2)
325         end
326       elsif array && line =~ /^         (\S[^:]+):\s+(.*\S)\s*$/
327         case Regexp.last_match(1)
328         when "Size" then array[:size] = Regexp.last_match(2)
329         when "Fault Tolerance" then array[:raid_level] = Regexp.last_match(2)
330         when "Disk Name" then array[:device] = Regexp.last_match(2).strip
331         when "Mount Points" then array[:mount_point] = Regexp.last_match(2).split.first
332         when "Unique Identifier" then array[:wwn] = Regexp.last_match(2)
333         end
334       end
335     end
336
337     controllers.each do |controller|
338       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/cciss*").first
339         controller[:device] = File.basename(device).sub(/^cciss(\d+)$/, "/dev/cciss/c\\1d0")
340       elsif device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target0:0:0/0:0:0:0/scsi_generic/sg*").first
341         controller[:device] = "/dev/#{File.basename(device)}"
342       end
343     end
344   end
345
346   def find_megaraid_disks(devices)
347     controllers = []
348     arrays = []
349
350     controller = nil
351     array = nil
352     disk = nil
353
354     IO.popen(%w(megacli -AdpGetPciInfo -aAll -NoLog)).each do |line|
355       if line =~ /^PCI information for Controller (\d+)$/
356         controller = {
357           :id => devices[:controllers].count,
358           :arrays => [],
359           :disks => []
360         }
361
362         devices[:controllers] << controller
363
364         controllers << controller
365       elsif line =~ /^Bus Number\s+:\s+(\d+)$/
366         controller[:pci_slot] = format "0000:%02x", Integer("0x#{Regexp.last_match(1)}")
367       elsif line =~ /^Device Number\s+:\s+(\d+)$/
368         controller[:pci_slot] = format "%s:%02x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
369       elsif line =~ /^Function Number\s+:\s+(\d+)$/
370         controller[:pci_slot] = format "%s.%01x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
371       end
372     end
373
374     IO.popen(%w(megacli -AdpAllInfo -aAll -NoLog)).each do |line|
375       if line =~ /^Adapter #(\d+)$/
376         controller = controllers[Regexp.last_match(1).to_i]
377       elsif line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
378         case Regexp.last_match(1)
379         when "Product Name" then controller[:model] = Regexp.last_match(2)
380         when "Serial No" then controller[:serial_number] = Regexp.last_match(2)
381         when "FW Package Build" then controller[:firmware_version] = Regexp.last_match(2)
382         end
383       end
384     end
385
386     IO.popen(%w(megacli -LdPdInfo -aAll -NoLog)).each do |line|
387       if line =~ /^Adapter #(\d+)$/
388         controller = controllers[Regexp.last_match(1).to_i]
389       elsif controller && line =~ /^Virtual Drive: (\d+) \(Target Id: (\d+)\)$/
390         pci_slot = controller[:pci_slot]
391         target = Regexp.last_match(2)
392         device = Dir.glob("/sys/bus/pci/devices/#{pci_slot}/host*/target*:2:#{target}/*:2:#{target}:0/block/*").first
393
394         array = {
395           :id => devices[:arrays].count,
396           :controller => controller[:id],
397           :number => Regexp.last_match(1),
398           :device => "/dev/#{File.basename(device)}",
399           :disks => []
400         }
401
402         devices[:arrays] << array
403         controller[:arrays] << array[:id]
404
405         arrays << array
406
407         disk = nil
408       elsif array && line =~ /^PD: (\d+) Information$/
409         disk = {
410           :id => devices[:disks].count,
411           :controller => controller[:id],
412           :arrays => [array[:id]]
413         }
414
415         devices[:disks] << disk
416         controller[:disks] << disk[:id]
417         array[:disks] << disk[:id]
418       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
419         case Regexp.last_match(1)
420         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
421         when "WWN" then disk[:wwn] = Regexp.last_match(2)
422         when "PD Type" then disk[:interface] = Regexp.last_match(2)
423         when "Raw Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2).sub(/\s*\[.*\]$/, ""))
424         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial_number] = Regexp.last_match(2).split
425         end
426       elsif array && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
427         case Regexp.last_match(1)
428         when "RAID Level" then array[:raid_level] = Regexp.last_match(2).scan(/Primary-(\d+)/).first.first
429         when "Size" then array[:size] = Regexp.last_match(2)
430         end
431       end
432     end
433
434     IO.popen(%w(megacli -PDList -aAll -NoLog)).each do |line|
435       if line =~ /^Adapter #(\d+)$/
436         controller = controllers[Regexp.last_match(1).to_i]
437       elsif controller && line =~ /^Enclosure Device ID: \d+$/
438         disk = {
439           :controller => controller[:id]
440         }
441       elsif disk && line =~ /^WWN:\s+(\S+)$/
442         unless devices[:disks].find { |d| d[:wwn] == Regexp.last_match(1) }
443           disk[:id] = devices[:disks].count
444           disk[:wwn] = Regexp.last_match(1)
445
446           devices[:disks] << disk
447         end
448       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
449         case Regexp.last_match(1)
450         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
451         when "WWN" then disk[:wwn] = Regexp.last_match(2)
452         when "PD Type" then disk[:interface] = Regexp.last_match(2)
453         when "Raw Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2).sub(/\s*\[.*\]$/, ""))
454         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial_number] = Regexp.last_match(2).split
455         end
456       end
457     end
458
459     controllers.each do |controller|
460       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target*:2:0/*/scsi_generic/sg*").first
461         controller[:device] = "/dev/#{File.basename(device)}"
462       end
463     end
464   end
465
466   def find_mpt_disks(devices)
467     controllers = []
468
469     IO.popen(%w(sas2ircu list)).each do |line|
470       next unless line =~ /^\s+(\d+)\s+(\S+)\s+\h+h\s+\h+h\s+(\S+)\s+\h+h\s+\h+h\s*$/
471       controllers[Regexp.last_match(1).to_i] = {
472         :id => devices[:controllers].count,
473         :model => Regexp.last_match(2),
474         :pci_slot => Regexp.last_match(3).sub(/^(\h{2})h:(\h{2})h:(\h{2})h:0(\h)h$/, "00\\1:\\2:\\3.\\4"),
475         :arrays => [],
476         :disks => []
477       }
478
479       devices[:controllers] << controllers[Regexp.last_match(1).to_i]
480     end
481
482     controllers.each_with_index do |controller, index|
483       arrays = []
484       disks = []
485
486       array = nil
487       disk = nil
488
489       IO.popen(["sas2ircu", index.to_s, "display"]).each do |line|
490         if line =~ /^IR volume (\d+)$/
491           array = {
492             :id => devices[:arrays].count,
493             :controller => controller[:id],
494             :number => Regexp.last_match(1),
495             :disks => []
496           }
497
498           devices[:arrays] << array
499           controller[:arrays] << array[:id]
500
501           arrays << array
502         elsif line =~ /^Device is a Hard disk$/
503           disk = {
504             :id => devices[:disks].count,
505             :controller => controller[:id],
506             :arrays => []
507           }
508
509           devices[:disks] << disk
510           controller[:disks] << disk[:id]
511
512           disks << disk
513         elsif disk && line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
514           case Regexp.last_match(1)
515           when "Enclosure #" then disk[:location] = Regexp.last_match(2)
516           when "Slot #" then disk[:location] = "#{disk[:location]}:#{Regexp.last_match(2)}"
517           when "SAS Address" then disk[:device] = find_sas_device(Regexp.last_match(2).tr("-", ""))
518           when "Size (in MB)/(in sectors)" then disk[:size] = memory_to_disk_size("#{Regexp.last_match(2).split('/').first} MB")
519           when "Manufacturer" then disk[:vendor] = Regexp.last_match(2)
520           when "Model Number" then disk[:model] = Regexp.last_match(2)
521           when "Firmware Revision" then disk[:firmware_version] = Regexp.last_match(2)
522           when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
523           when "Protocol" then disk[:interface] = Regexp.last_match(2)
524           end
525         elsif array && line =~ /^  PHY\[\d+\] Enclosure#\/Slot#\s+:\s+(\d+:\d+)\s*$/
526           array[:disks] << Regexp.last_match(1)
527         elsif array && line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
528           case Regexp.last_match(1)
529           when "Volume wwid" then array[:device] = find_sas_device(Regexp.last_match(2))
530           when "RAID level" then array[:raid_level] = Regexp.last_match(2).sub(/^RAID/, "")
531           when "Size (in MB)" then array[:size] = "#{Regexp.last_match(2)} MB"
532           end
533         elsif line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
534           case Regexp.last_match(1)
535           when "BIOS version" then controller[:bios_version] = Regexp.last_match(2)
536           when "Firmware version" then controller[:firmware_version] = Regexp.last_match(2)
537           end
538         end
539       end
540
541       arrays.each do |array|
542         array[:disks].map! do |location|
543           disk = disks.find { |disk| disk[:location] == location }
544
545           disk[:arrays] << array[:id]
546           disk[:id]
547         end
548       end
549     end
550   end
551
552   def find_adaptec_disks(devices)
553     controller_count = IO.popen(%w(arcconf getconfig 0)).first.scan(/^Controllers found: (\d+)$/).first.first.to_i
554
555     1.upto(controller_count).each do |controller_number|
556       controller = {
557         :id => devices[:controllers].count,
558         :number => controller_number,
559         :arrays => [],
560         :disks => []
561       }
562
563       devices[:controllers] << controller
564
565       arrays = []
566       disks = []
567
568       array = nil
569       disk = nil
570
571       IO.popen(["arcconf", "getconfig", controller_number.to_s]).each do |line|
572         if line =~ /^Logical device number (\d+)$/
573           array = {
574             :id => devices[:arrays].count,
575             :controller => controller[:id],
576             :number => Regexp.last_match(1).to_i,
577             :disks => []
578           }
579
580           devices[:arrays] << array
581           controller[:arrays] << array[:id]
582
583           arrays << array
584         elsif line =~ /^      Device #(\d+)$/
585           disk = nil
586         elsif line =~ /^         Device is a Hard drive$/
587           disk = {
588             :id => devices[:disks].count,
589             :controller => controller[:id],
590             :arrays => []
591           }
592
593           devices[:disks] << disk
594           controller[:disks] << disk[:id]
595
596           disks << disk
597         elsif disk && line =~ /^         Reported Channel,Device\(T:L\)\s*:\s+(\d+),(\d+)\(\d+:0\)\s*$/
598           disk[:channel_number] = Regexp.last_match(1)
599           disk[:device_number] = Regexp.last_match(1)
600         elsif disk && line =~ /^         (\S.*\S)\s*:\s+(\S.*\S)\s*$/
601           case Regexp.last_match(1)
602           when "Reported Location" then disk[:location] = Regexp.last_match(2)
603           when "Vendor" then disk[:vendor] = Regexp.last_match(2)
604           when "Model" then disk[:model] = Regexp.last_match(2)
605           when "Firmware" then disk[:firmware_version] = Regexp.last_match(2)
606           when "Serial" then disk[:serial_number] = Regexp.last_match(2)
607           when "World-wide name" then disk[:wwn] = Regexp.last_match(2)
608           when "Total Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2))
609           when "Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2))
610           end
611         elsif array && line =~ / Present \(Controller:\d+,((?:Connector|Enclosure):\d+,(?:Device|Slot):\d+)\) /
612           array[:disks] << Regexp.last_match(1).tr(":", " ").gsub(",", ", ")
613         elsif array && line =~ /^   (\S.*\S)\s*:\s+(\S.*\S)\s*$/
614           case Regexp.last_match(1)
615           when "RAID level" then array[:raid_level] = Regexp.last_match(2)
616           when "Size" then array[:size] = memory_to_disk_size(Regexp.last_match(2))
617           end
618         elsif line =~ /^   (\S.*\S)\s*:\s+(\S.*\S)\s*$/
619           case Regexp.last_match(1)
620           when "Controller Model" then controller[:model] = Regexp.last_match(2)
621           when "Controller Serial Number" then controller[:serial_number] = Regexp.last_match(2)
622           when "Controller World Wide Name" then controller[:wwn] = Regexp.last_match(2)
623           when "BIOS" then controller[:bios_version] = Regexp.last_match(2)
624           when "Firmware" then controller[:firmware_version] = Regexp.last_match(2)
625           end
626         end
627       end
628
629       host = Dir.glob("/sys/class/scsi_host/host*").find do |host|
630         read_sysctl_file("#{host}/serial_number") == controller[:serial_number]
631       end
632
633       arrays.each do |array|
634         array_number = array[:number]
635         device = Dir.glob("#{host}/device/target*:0:#{array_number}/*:0:#{array_number}:0/block/*").first
636
637         array[:device] = "/dev/#{File.basename(device)}"
638
639         array[:disks].map! do |location|
640           disk = disks.find { |disk| disk[:location] == location }
641
642           device_number = disk[:device_number]
643           device = Dir.glob("#{host}/device/target*:1:#{device_number}/*:1:#{device_number}:0/scsi_generic/*").first
644
645           disk[:device] = "/dev/#{File.basename(device)}"
646
647           disk[:arrays] << array[:id]
648           disk[:id]
649         end
650       end
651     end
652   end
653
654   def find_areca_disks(devices)
655     controller = {
656       :id => devices[:controllers].count,
657       :arrays => [],
658       :disks => []
659     }
660
661     devices[:controllers] << controller
662
663     IO.popen(%w(/opt/areca/x86_64/cli64 sys info)).each do |line|
664       next unless line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
665
666       case Regexp.last_match(1)
667       when "Firmware Version" then controller[:firmware_version] = Regexp.last_match(2)
668       when "BOOT ROM Version" then controller[:bios_version] = Regexp.last_match(2)
669       when "Serial Number" then controller[:serial_number] = Regexp.last_match(2)
670       when "Controller Name" then controller[:model] = Regexp.last_match(2)
671       end
672     end
673
674     path = Dir.glob("/sys/bus/pci/devices/*/host*/scsi_host/host*/host_fw_model").find do |file|
675       read_sysctl_file(file) == controller[:model]
676     end
677
678     controller[:pci_slot] = File.basename(File.expand_path("#{path}/../../../.."))
679     controller[:device] = File.basename(Dir.glob(File.expand_path("#{path}/../../../target0:0:16/0:0:16:0/scsi_generic/*")).first)
680
681     arrays = []
682
683     IO.popen(%w(/opt/areca/x86_64/cli64 vsf info)).each do |line|
684       next unless line =~ /^\s+(\d+)\s+/
685       array = {
686         :id => devices[:arrays].count,
687         :number => Regexp.last_match(1),
688         :controller => controller[:id],
689         :disks => []
690       }
691
692       devices[:arrays] << array
693       controller[:arrays] << array[:id]
694
695       arrays << array
696     end
697
698     arrays.each do |array|
699       IO.popen(["/opt/areca/x86_64/cli64", "vsf", "info", "vol=#{array[:number]}"]).each do |line|
700         if line =~ /^SCSI Ch\/Id\/Lun\s+:\s+(\d+)\/(\d+)\/(\d+)\s*$/
701           pci_slot = controller[:pci_slot]
702           channel = Regexp.last_match(1).to_i
703           id = Regexp.last_match(2).to_i
704           lun = Regexp.last_match(3).to_i
705
706           device = Dir.glob("/sys/bus/pci/devices/#{pci_slot}/host*/target*:0:0/0:#{channel}:#{id}:#{lun}/block/*").first
707
708           array[:device] = "/dev/#{File.basename(device)}"
709         elsif line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
710           case Regexp.last_match(1)
711           when "Volume Set Name" then array[:volume_set] = Regexp.last_match(2)
712           when "Raid Set Name" then array[:raid_set] = Regexp.last_match(2)
713           when "Volume Capacity" then array[:size] = format_disk_size(Regexp.last_match(2).to_f * 1000 * 1000)
714           when "Raid Level" then array[:raid_level] = Regexp.last_match(2).sub(/^Raid/, "")
715           end
716         end
717       end
718     end
719
720     disks = []
721
722     IO.popen(%w(/opt/areca/x86_64/cli64 disk info)).each do |line|
723       next unless line =~ /^\s+(\d+)\s+.*\s+\d+\.\d+GB\s+(\S.*\S)\s*$/
724       next if Regexp.last_match(2) == "N.A."
725
726       disk = {
727         :id => devices[:disks].count,
728         :number => Regexp.last_match(1),
729         :controller => controller[:id],
730         :arrays => []
731       }
732
733       devices[:disks] << disk
734       controller[:disks] << disk[:id]
735
736       if array = arrays.find { |array| array[:raid_set] == Regexp.last_match(2) }
737         disk[:arrays] << array[:id]
738         array[:disks] << disk[:id]
739       end
740
741       disks << disk
742     end
743
744     disks.each do |disk|
745       IO.popen(["/opt/areca/x86_64/cli64", "disk", "info", "drv=#{disk[:number]}"]).each do |line|
746         if line =~ /^IDE Channel\s+:\s+(\d+)\s*$/i
747           disk[:smart_device] = "areca,#{Regexp.last_match(1)}"
748         elsif line =~ /^Device Location\s+:\s+Enclosure#(\d+) Slot#?\s*0*(\d+)\s*$/i
749           disk[:smart_device] = "areca,#{Regexp.last_match(2)}/#{Regexp.last_match(1)}"
750         elsif line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
751           case Regexp.last_match(1)
752           when "Model Name" then disk[:vendor], disk[:model] = Regexp.last_match(2).split
753           when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
754           when "Disk Capacity" then disk[:size] = format_disk_size(Regexp.last_match(2).to_f * 1000 * 1000)
755           end
756         end
757       end
758     end
759   end
760
761   def psu_devices
762     device = nil
763
764     IO.popen(["dmidecode", "-t", "39"]).each_with_object([]) do |line, devices|
765       if line =~ /^System Power Supply\s*$/
766         device = {}
767       elsif device && line =~ /^\s+([A-Z ]+):\s+(.*)\s*$/i
768         device[Regexp.last_match(1).tr(" ", "_").downcase.to_sym] = Regexp.last_match(2).strip
769       elsif device && line =~ /^\s*$/
770         devices << device
771         device = nil
772       end
773     end
774   end
775
776   collect_data(:default) do
777     hardware Mash.new
778
779     hardware[:pci] = pci_devices
780     hardware[:network] = network_devices
781     hardware[:memory] = memory_devices
782     hardware[:disk] = disk_devices
783     hardware[:psu] = psu_devices
784   end
785 end