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