]> git.openstreetmap.org Git - chef.git/blob - cookbooks/hardware/templates/default/ohai.rb.erb
Gather information on megaraid disk controllers
[chef.git] / cookbooks / hardware / templates / default / ohai.rb.erb
1 Ohai.plugin(:Hardware) do
2   provides "hardware"
3
4   def read_sysctl_link(file)
5     File.basename(File.readlink(file))
6   rescue Errno::ENOENT
7   end
8
9   def read_sysctl_file(file)
10     IO.read(file).strip
11   rescue Errno::ENOENT, Errno::EINVAL
12   end
13
14   def pci_devices
15     device = nil
16
17     IO.popen(["lspci", "-Dkvmm"]).each_with_object(Mash.new) do |line, devices|
18       if line =~ /^Slot:\s+((\h{4}):(\h{2}):(\h{2}).(\h))\s*$/
19         device = {
20           :slot => Regexp.last_match(1),
21           :domain => Regexp.last_match(2),
22           :bus => Regexp.last_match(3),
23           :device => Regexp.last_match(4),
24           :function => Regexp.last_match(5)
25         }
26       elsif device && line =~ /^([A-Z]+):\s+(.*)\s*$/i
27         case Regexp.last_match(1)
28         when "Class" then device[:class_name] = Regexp.last_match(2)
29         when "Vendor" then device[:vendor_name] = Regexp.last_match(2)
30         when "Device" then device[:device_name] = Regexp.last_match(2)
31         when "SVendor" then device[:subsystem_vendor_name] = Regexp.last_match(2)
32         when "SDevice" then device[:subsystem_device_name] = Regexp.last_match(2)
33         when "PhySlot" then device[:physical_slot] = Regexp.last_match(2)
34         when "Rev" then device[:revision] = Regexp.last_match(2)
35         when "ProgIf" then device[:programming_interface] = Regexp.last_match(2)
36         when "Driver" then device[:driver] = Regexp.last_match(2)
37         when "Module" then device[:modules] = Array(device[:modules]) << Regexp.last_match(2)
38         end
39       elsif device && line =~ /^\s*$/
40         devices[device[:slot]] = device
41         device = nil
42       end
43     end
44   end
45
46   def network_devices
47     Dir.glob("/sys/class/net/*").each_with_object(Mash.new) do |device, devices|
48       name = File.basename(device)
49
50       devices[name] = {
51         :device => read_sysctl_link("#{device}/device"),
52         :duplex => read_sysctl_file("#{device}/duplex"),
53         :speed => read_sysctl_file("#{device}/speed")
54       }.delete_if { |_, v| v.nil? }
55     end
56   end
57
58   def memory_devices
59     device = nil
60
61     IO.popen(["dmidecode", "-t", "memory"]).each_with_object([]) do |line, devices|
62       if line =~ /^Memory Device\s*$/
63         device = {}
64       elsif device && line =~ /^\s+([A-Z ]+):\s+(.*)\s*$/i
65         device[Regexp.last_match(1).tr(" ", "_").downcase.to_sym] = Regexp.last_match(2).strip
66       elsif device && line =~ /^\s*$/
67         devices << device
68         device = nil
69       end
70     end
71   end
72
73   def disk_devices
74     disk = Mash.new
75
76     disk[:controllers] = []
77     disk[:arrays] = []
78     disk[:disks] = []
79
80     find_hp_disks(disk) if File.exist?("/usr/sbin/hpssacli")
81     find_megaraid_disks(disk) if File.exist?("/usr/sbin/megacli")
82
83     disk
84   end
85
86   def find_hp_disks(devices)
87     controllers = []
88     disks = []
89
90     controller = nil
91     array = nil
92     disk = nil
93
94     IO.popen(%w(hpssacli controller all show config detail)).each do |line|
95       if line =~ /^Smart Array (\S+) /
96         controller = {
97           :id => devices[:controllers].count,
98           :model => Regexp.last_match(1),
99           :arrays => [],
100           :disks => []
101         }
102
103         devices[:controllers] << controller
104
105         controllers << controller
106
107         array = nil
108         disk = nil
109       elsif controller && line =~ /^   (\S.*):\s+(.*)$/
110         case Regexp.last_match(1)
111         when "Serial Number" then controller[:serial_number] = Regexp.last_match(2)
112         when "Hardware Revision" then controller[:hardware_version] = Regexp.last_match(2)
113         when "Firmware Version" then controller[:firmware_version] = Regexp.last_match(2)
114         when "PCI Address (Domain:Bus:Device.Function)" then controller[:pci_slot] = Regexp.last_match(2)
115         end
116       elsif controller && line =~ /^      Logical Drive: (\d+)$/
117         array = {
118           :id => devices[:arrays].count,
119           :controller => controller[:id],
120           :number => Regexp.last_match(1),
121           :disks => []
122         }
123
124         devices[:arrays] << array
125         controller[:arrays] << array[:id]
126
127         disk = nil
128       elsif controller && line =~ /^      physicaldrive (\S+) /
129         disks << Regexp.last_match(1)
130       elsif array && line =~ /^      physicaldrive (\S+)$/
131         disk = {
132           :id => devices[:disks].count,
133           :controller => controller[:id],
134           :array => array[:id],
135           :location => Regexp.last_match(1),
136           :smart_device => "cciss,#{disks.find_index(Regexp.last_match(1))}"
137         }
138
139         devices[:disks] << disk
140         controller[:disks] << disk[:id]
141         array[:disks] << disk[:id]
142       elsif disk && line =~ /^         (\S[^:]+):\s+(.*)$/
143         case Regexp.last_match(1)
144         when "Interface Type" then disk[:interface] = Regexp.last_match(2)
145         when "Size" then disk[:size] = Regexp.last_match(2)
146         when "Rotational Speed" then disk[:rpm] = Regexp.last_match(2)
147         when "Firmware Revision" then disk[:firmware_version] = Regexp.last_match(2)
148         when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
149         when "Model" then disk[:vendor], disk[:model] = Regexp.last_match(2).squeeze(" ").strip.sub(/^ATA /, "").split
150         end
151       elsif array && line =~ /^         (\S[^:]+):\s+(.*)$/
152         case Regexp.last_match(1)
153         when "Size" then array[:size] = Regexp.last_match(2)
154         when "Fault Tolerance" then array[:raid_level] = Regexp.last_match(2)
155         when "Disk Name" then array[:device] = Regexp.last_match(2).strip
156         when "Mount Points" then array[:mount_point] = Regexp.last_match(2).split.first
157         when "Unique Identifier" then array[:wwn] = Regexp.last_match(2)
158         end
159       end
160     end
161
162     controllers.each do |controller|
163       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/cciss*").first
164         controller[:device] = File.basename(device).sub(/^cciss(\d+)$/, "/dev/cciss/c\\1d0")
165       elsif device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target0:0:0/0:0:0:0/scsi_generic/sg*").first
166         controller[:device] = "/dev/#{File.basename(device)}"
167       end
168     end
169   end
170
171   def find_megaraid_disks(devices)
172     controllers = []
173     arrays = []
174
175     controller = nil
176     array = nil
177     disk = nil
178
179     IO.popen(%w(megacli -AdpGetPciInfo -aAll -NoLog)).each do |line|
180       if line =~ /^PCI information for Controller (\d+)$/
181         controller = {
182           :id => devices[:controllers].count,
183           :arrays => [],
184           :disks => []
185         }
186
187         devices[:controllers] << controller
188
189         controllers << controller
190       elsif line =~ /^Bus Number\s+:\s+(\d+)$/
191         controller[:pci_slot] = sprintf "0000:%02x", Integer("0x#{Regexp.last_match(1)}")
192       elsif line =~ /^Device Number\s+:\s+(\d+)$/
193         controller[:pci_slot] = sprintf "%s:%02x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
194       elsif line =~ /^Function Number\s+:\s+(\d+)$/
195         controller[:pci_slot] = sprintf "%s.%01x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
196       end
197     end
198
199     IO.popen(%w(megacli -AdpAllInfo -aAll -NoLog)).each do |line|
200       if line =~ /^Adapter #(\d+)$/
201         controller = controllers[Regexp.last_match(1).to_i]
202       elsif line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
203         case Regexp.last_match(1)
204         when "Product Name" then controller[:model] = Regexp.last_match(2)
205         when "Serial No" then controller[:serial_number] = Regexp.last_match(2)
206         when "FW Package Build" then controller[:firmware_version] = Regexp.last_match(2)
207         end
208       end
209     end
210
211     IO.popen(%w(megacli -LdPdInfo -aAll -NoLog)).each do |line|
212       if line =~ /^Adapter #(\d+)$/
213         controller = controllers[Regexp.last_match(1).to_i]
214       elsif controller && line =~ /^Virtual Drive: (\d+) \(Target Id: (\d+)\)$/
215         array = {
216           :id => devices[:arrays].count,
217           :controller => controller[:id],
218           :number => Regexp.last_match(1),
219           :disks => []
220         }
221
222         devices[:arrays] << array
223         controller[:arrays] << array[:id]
224
225         arrays << array
226
227         disk = nil
228       elsif array && line =~ /^PD: (\d+) Information$/
229         disk = {
230           :id => devices[:disks].count,
231           :controller => controller[:id],
232           :array => array[:id]
233         }
234
235         devices[:disks] << disk
236         controller[:disks] << disk[:id]
237         array[:disks] << disk[:id]
238       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
239         case Regexp.last_match(1)
240         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
241         when "WWN" then disk[:wwn] = Regexp.last_match(2)
242         when "PD Type" then disk[:interface] = Regexp.last_match(2)
243         when "Raw Size" then disk[:size] = Regexp.last_match(2).sub(/\s*\[.*\]$/, "")
244         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial] = Regexp.last_match(2).split
245         end
246       elsif array && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
247         case Regexp.last_match(1)
248         when "RAID Level" then array[:raid_level] = Regexp.last_match(2).scan(/Primary-(\d+)/).first.first
249         when "Size" then array[:size] = Regexp.last_match(2)
250         end
251       end
252     end
253
254     IO.popen(%w(megacli -PDList -aAll -NoLog)).each do |line|
255       if line =~ /^Adapter #(\d+)$/
256         controller = controllers[Regexp.last_match(1).to_i]
257       elsif controller && line =~ /^Enclosure Device ID: \d+$/
258         disk = {
259           :controller => controller[:id]
260         }
261       elsif disk && line =~ /^WWN:\s+(\S+)$/
262         unless devices[:disks].find { |d| d[:wwn] == Regexp.last_match(1) }
263           disk[:id] = devices[:disks].count
264           disk[:wwn] = Regexp.last_match(1)
265
266           devices[:disks] << disk
267         end
268       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
269         case Regexp.last_match(1)
270         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
271         when "WWN" then disk[:wwn] = Regexp.last_match(2)
272         when "PD Type" then disk[:interface] = Regexp.last_match(2)
273         when "Raw Size" then disk[:size] = Regexp.last_match(2).sub(/\s*\[.*\]$/, "")
274         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial] = Regexp.last_match(2).split
275         end
276       end
277     end
278
279     controllers.each do |controller|
280       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target*:2:0/*/scsi_generic/sg*").first
281         controller[:device] = "/dev/#{File.basename(device)}"
282       end
283     end
284   end
285
286   collect_data(:default) do
287     hardware Mash.new
288
289     hardware[:pci] = pci_devices
290     hardware[:network] = network_devices
291     hardware[:memory] = memory_devices
292     hardware[:disk] = disk_devices
293   end
294 end