]> git.openstreetmap.org Git - chef.git/blob - cookbooks/hardware/recipes/default.rb
656bb58ba4ad62f72e3279b052f0eb3ff6c127b2
[chef.git] / cookbooks / hardware / recipes / default.rb
1 #
2 # Cookbook:: hardware
3 # Recipe:: default
4 #
5 # Copyright:: 2012, OpenStreetMap Foundation
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 #     https://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19
20 include_recipe "git"
21 include_recipe "munin"
22 include_recipe "prometheus"
23 include_recipe "sysfs"
24 include_recipe "tools"
25
26 ohai_plugin "hardware" do
27   template "ohai.rb.erb"
28 end
29
30 case node[:cpu][:"0"][:vendor_id]
31 when "GenuineIntel"
32   package "intel-microcode"
33 when "AuthenticAMD"
34   package "amd64-microcode"
35 end
36
37 if node[:dmi] && node[:dmi][:system]
38   case node[:dmi][:system][:manufacturer]
39   when "empty"
40     manufacturer = node[:dmi][:base_board][:manufacturer]
41     product = node[:dmi][:base_board][:product_name]
42   else
43     manufacturer = node[:dmi][:system][:manufacturer]
44     product = node[:dmi][:system][:product_name]
45   end
46 else
47   manufacturer = "Unknown"
48   product = "Unknown"
49 end
50
51 units = []
52
53 if node[:roles].include?("bytemark") || node[:roles].include?("exonetric") || node[:roles].include?("prgmr")
54   units << "0"
55 end
56
57 case manufacturer
58 when "HP", "HPE"
59   include_recipe "apt::management-component-pack"
60
61   package "hponcfg"
62
63   execute "update-ilo" do
64     action :nothing
65     command "/usr/sbin/hponcfg -f /etc/ilo-defaults.xml"
66   end
67
68   template "/etc/ilo-defaults.xml" do
69     source "ilo-defaults.xml.erb"
70     owner "root"
71     group "root"
72     mode "644"
73     notifies :run, "execute[update-ilo]"
74   end
75
76   package "hp-health" do
77     action :install
78     notifies :restart, "service[hp-health]"
79     only_if { node[:lsb][:release].to_f < 22.04 }
80   end
81
82   service "hp-health" do
83     action [:enable, :start]
84     supports :status => true, :restart => true
85     only_if { node[:lsb][:release].to_f < 22.04 }
86   end
87
88   if product.end_with?("Gen8", "Gen9")
89     package "hp-ams" do
90       action :install
91       notifies :restart, "service[hp-ams]"
92     end
93
94     service "hp-ams" do
95       action [:enable, :start]
96       supports :status => true, :restart => true
97     end
98   elsif product.end_with?("Gen10")
99     package "amsd" do
100       action :install
101       notifies :restart, "service[amsd]"
102     end
103
104     service "amsd" do
105       action [:enable, :start]
106       supports :status => true, :restart => true
107     end
108   end
109
110   units << if product.end_with?("Gen10")
111              "0"
112            else
113              "1"
114            end
115 when "TYAN"
116   units << "0"
117 when "TYAN Computer Corporation"
118   units << "0"
119 when "Supermicro"
120   units << "1"
121 when "IBM"
122   units << "0"
123 when "VMware, Inc."
124   package "open-vm-tools"
125
126   # Remove timeSync plugin completely
127   # https://github.com/vmware/open-vm-tools/issues/302
128   file "/usr/lib/open-vm-tools/plugins/vmsvc/libtimeSync.so" do
129     action :delete
130     notifies :restart, "service[open-vm-tools]"
131   end
132
133   # Attempt to tell Host we are not interested in timeSync
134   execute "vmware-toolbox-cmd-timesync-disable" do
135     command "/usr/bin/vmware-toolbox-cmd timesync disable"
136     ignore_failure true
137   end
138
139   service "open-vm-tools" do
140     action [:enable, :start]
141     supports :status => true, :restart => true
142   end
143 end
144
145 units.sort.uniq.each do |unit|
146   service "serial-getty@ttyS#{unit}" do
147     action [:enable, :start]
148   end
149 end
150
151 # if we need a different / special kernel version to make the hardware
152 # work (e.g: https://github.com/openstreetmap/operations/issues/45) then
153 # ensure that we have the package installed. the grub template will
154 # make sure that this is the default on boot.
155 if node[:hardware][:grub][:kernel]
156   kernel_version = node[:hardware][:grub][:kernel]
157
158   package "linux-image-#{kernel_version}-generic"
159   package "linux-image-extra-#{kernel_version}-generic"
160   package "linux-headers-#{kernel_version}-generic"
161   package "linux-tools-#{kernel_version}-generic"
162
163   boot_device = IO.popen(["df", "/boot"]).readlines.last.split.first
164   boot_uuid = IO.popen(["blkid", "-o", "value", "-s", "UUID", boot_device]).readlines.first.chomp
165   grub_entry = "gnulinux-advanced-#{boot_uuid}>gnulinux-#{kernel_version}-advanced-#{boot_uuid}"
166 else
167   grub_entry = "0"
168 end
169
170 if File.exist?("/etc/default/grub")
171   execute "update-grub" do
172     action :nothing
173     command "/usr/sbin/update-grub"
174     not_if { kitchen? }
175   end
176
177   template "/etc/default/grub" do
178     source "grub.erb"
179     owner "root"
180     group "root"
181     mode "644"
182     variables :units => units, :entry => grub_entry
183     notifies :run, "execute[update-grub]"
184   end
185 end
186
187 execute "update-initramfs" do
188   action :nothing
189   command "update-initramfs -u -k all"
190   user "root"
191   group "root"
192 end
193
194 template "/etc/initramfs-tools/conf.d/mdadm" do
195   source "initramfs-mdadm.erb"
196   owner "root"
197   group "root"
198   mode "644"
199   notifies :run, "execute[update-initramfs]"
200 end
201
202 package "haveged"
203 service "haveged" do
204   action [:enable, :start]
205 end
206
207 if node[:kernel][:modules].include?("ipmi_si")
208   package "ipmitool"
209   package "freeipmi-tools"
210
211   template "/etc/prometheus/ipmi_local.yml" do
212     source "ipmi_local.yml.erb"
213     owner "root"
214     group "root"
215     mode "644"
216   end
217
218   prometheus_exporter "ipmi" do
219     port 9290
220     options "--config.file=/etc/prometheus/ipmi_local.yml"
221     subscribes :restart, "template[/etc/prometheus/ipmi_local.yml]"
222   end
223 end
224
225 package "irqbalance"
226
227 service "irqbalance" do
228   action [:start, :enable]
229   supports :status => false, :restart => true, :reload => false
230 end
231
232 package "lldpd"
233
234 service "lldpd" do
235   action [:start, :enable]
236   supports :status => true, :restart => true, :reload => true
237 end
238
239 ohai_plugin "lldp" do
240   template "lldp.rb.erb"
241 end
242
243 package %w[
244   rasdaemon
245   ruby-sqlite3
246 ]
247
248 service "rasdaemon" do
249   action [:enable, :start]
250 end
251
252 prometheus_exporter "rasdaemon" do
253   port 9797
254 end
255
256 tools_packages = []
257 status_packages = {}
258
259 if node[:virtualization][:role] != "guest" ||
260    (node[:virtualization][:system] != "lxc" &&
261     node[:virtualization][:system] != "lxd" &&
262     node[:virtualization][:system] != "openvz")
263
264   node[:kernel][:modules].each_key do |modname|
265     case modname
266     when "cciss"
267       tools_packages << "ssacli"
268       status_packages["cciss-vol-status"] ||= []
269     when "hpsa"
270       tools_packages << "ssacli"
271       status_packages["cciss-vol-status"] ||= []
272     when "mptsas"
273       tools_packages << "lsiutil"
274       status_packages["mpt-status"] ||= []
275     when "mpt2sas", "mpt3sas"
276       tools_packages << "sas2ircu"
277       status_packages["sas2ircu-status"] ||= []
278     when "megaraid_sas"
279       tools_packages << "megacli"
280       status_packages["megaclisas-status"] ||= []
281     when "aacraid"
282       tools_packages << "arcconf"
283       status_packages["aacraid-status"] ||= []
284     when "arcmsr"
285       tools_packages << "areca"
286     end
287   end
288
289   node[:block_device].each do |name, attributes|
290     next unless attributes[:vendor] == "HP" && attributes[:model] == "LOGICAL VOLUME"
291
292     if name =~ /^cciss!(c[0-9]+)d[0-9]+$/
293       status_packages["cciss-vol-status"] |= ["cciss/#{Regexp.last_match[1]}d0"]
294     else
295       Dir.glob("/sys/block/#{name}/device/scsi_generic/*").each do |sg|
296         status_packages["cciss-vol-status"] |= [File.basename(sg)]
297       end
298     end
299   end
300 end
301
302 %w[ssacli lsiutil sas2ircu megactl megacli arcconf].each do |tools_package|
303   if tools_packages.include?(tools_package)
304     package tools_package
305   else
306     package tools_package do
307       action :purge
308     end
309   end
310 end
311
312 if tools_packages.include?("areca")
313   include_recipe "git"
314
315   git "/opt/areca" do
316     action :sync
317     repository "https://git.openstreetmap.org/private/areca.git"
318     depth 1
319     user "root"
320     group "root"
321     not_if { kitchen? }
322   end
323 else
324   directory "/opt/areca" do
325     action :delete
326     recursive true
327   end
328 end
329
330 include_recipe "apt::hwraid" unless status_packages.empty?
331
332 if status_packages.include?("cciss-vol-status")
333   template "/usr/local/bin/cciss-vol-statusd" do
334     source "cciss-vol-statusd.erb"
335     owner "root"
336     group "root"
337     mode "755"
338     notifies :restart, "service[cciss-vol-statusd]"
339   end
340
341   systemd_service "cciss-vol-statusd" do
342     description "Check cciss_vol_status values in the background"
343     exec_start "/usr/local/bin/cciss-vol-statusd"
344     nice 10
345     private_tmp true
346     protect_system "full"
347     protect_home true
348     no_new_privileges true
349     notifies :restart, "service[cciss-vol-statusd]"
350   end
351 else
352   systemd_service "cciss-vol-statusd" do
353     action :delete
354   end
355
356   template "/usr/local/bin/cciss-vol-statusd" do
357     action :delete
358   end
359 end
360
361 %w[cciss-vol-status mpt-status sas2ircu-status megaclisas-status aacraid-status].each do |status_package|
362   if status_packages.include?(status_package)
363     package status_package
364
365     template "/etc/default/#{status_package}d" do
366       source "raid.default.erb"
367       owner "root"
368       group "root"
369       mode "644"
370       variables :devices => status_packages[status_package]
371     end
372
373     service "#{status_package}d" do
374       action [:start, :enable]
375       supports :status => false, :restart => true, :reload => false
376       subscribes :restart, "template[/etc/default/#{status_package}d]"
377     end
378   else
379     package status_package do
380       action :purge
381     end
382
383     file "/etc/default/#{status_package}d" do
384       action :delete
385     end
386   end
387 end
388
389 disks = if node[:hardware][:disk]
390           node[:hardware][:disk][:disks]
391         else
392           []
393         end
394
395 intel_ssds = disks.select { |d| d[:vendor] == "INTEL" && d[:model] =~ /^SSD/ }
396
397 nvmes = if node[:hardware][:pci]
398           node[:hardware][:pci].values.select { |pci| pci[:driver] == "nvme" }
399         else
400           []
401         end
402
403 unless nvmes.empty?
404   package "nvme-cli"
405 end
406
407 intel_nvmes = nvmes.select { |pci| pci[:vendor_name] == "Intel Corporation" }
408
409 if !intel_ssds.empty? || !intel_nvmes.empty?
410   package "unzip"
411
412   sst_tool_version = "1.3"
413   sst_package_version = "#{sst_tool_version}.208-0"
414
415   remote_file "#{Chef::Config[:file_cache_path]}/SST_CLI_Linux_#{sst_tool_version}.zip" do
416     source "https://downloadmirror.intel.com/743764/SST_CLI_Linux_#{sst_tool_version}.zip"
417   end
418
419   execute "#{Chef::Config[:file_cache_path]}/SST_CLI_Linux_#{sst_tool_version}.zip" do
420     command "unzip SST_CLI_Linux_#{sst_tool_version}.zip sst_#{sst_package_version}_amd64.deb"
421     cwd Chef::Config[:file_cache_path]
422     user "root"
423     group "root"
424     not_if { ::File.exist?("#{Chef::Config[:file_cache_path]}/sst_#{sst_package_version}_amd64.deb") }
425   end
426
427   dpkg_package "sst" do
428     version "#{sst_package_version}"
429     source "#{Chef::Config[:file_cache_path]}/sst_#{sst_package_version}_amd64.deb"
430   end
431
432   dpkg_package "intelmas" do
433     action :purge
434   end
435 end
436
437 disks = disks.map do |disk|
438   next if disk[:state] == "spun_down" || %w[unconfigured failed].any?(disk[:status])
439
440   if disk[:smart_device]
441     controller = node[:hardware][:disk][:controllers][disk[:controller]]
442
443     if controller && controller[:device]
444       device = controller[:device].sub("/dev/", "")
445       smart = disk[:smart_device]
446
447       if device.start_with?("cciss/") && smart =~ /^cciss,(\d+)$/
448         array = node[:hardware][:disk][:arrays][disk[:arrays].first]
449         munin = "cciss-3#{array[:wwn]}-#{Regexp.last_match(1)}"
450       elsif smart =~ /^.*,(\d+)$/
451         munin = "#{device}-#{Regexp.last_match(1)}"
452       elsif smart =~ %r{^.*,(\d+)/(\d+)$}
453         munin = "#{device}-#{Regexp.last_match(1)}:#{Regexp.last_match(2)}"
454       end
455     elsif disk[:device]
456       device = disk[:device].sub("/dev/", "")
457       smart = disk[:smart_device]
458
459       if smart =~ /^.*,(\d+),(\d+),(\d+)$/
460         munin = "#{device}-#{Regexp.last_match(1)}:#{Regexp.last_match(2)}:#{Regexp.last_match(3)}"
461       end
462     end
463   elsif disk[:device] =~ %r{^/dev/(nvme\d+)n\d+$}
464     device = Regexp.last_match(1)
465     munin = device
466   elsif disk[:device]
467     device = disk[:device].sub("/dev/", "")
468     munin = device
469   end
470
471   next if device.nil?
472
473   Hash[
474     :device => device,
475     :smart => smart,
476     :munin => munin,
477     :hddtemp => munin.tr("-:", "_")
478   ]
479 end
480
481 disks = disks.compact.uniq
482
483 if disks.count.positive?
484   package "smartmontools"
485
486   template "/etc/cron.daily/update-smart-drivedb" do
487     source "update-smart-drivedb.erb"
488     owner "root"
489     group "root"
490     mode "755"
491   end
492
493   template "/usr/local/bin/smartd-mailer" do
494     source "smartd-mailer.erb"
495     owner "root"
496     group "root"
497     mode "755"
498   end
499
500   template "/etc/smartd.conf" do
501     source "smartd.conf.erb"
502     owner "root"
503     group "root"
504     mode "644"
505     variables :disks => disks
506   end
507
508   template "/etc/default/smartmontools" do
509     source "smartmontools.erb"
510     owner "root"
511     group "root"
512     mode "644"
513   end
514
515   service "smartmontools" do
516     action [:enable, :start]
517     subscribes :reload, "template[/etc/smartd.conf]"
518     subscribes :restart, "template[/etc/default/smartmontools]"
519   end
520
521   template "/etc/prometheus/collectors/smart.devices" do
522     source "smart.devices.erb"
523     owner "root"
524     group "root"
525     mode "644"
526     variables :disks => disks
527   end
528
529   prometheus_collector "smart" do
530     interval "15m"
531   end
532
533   # Don't try and do munin monitoring of disks behind
534   # an Areca controller as they only allow one thing to
535   # talk to the controller at a time and smartd will
536   # throw errors if it clashes with munin
537   disks = disks.reject { |disk| disk[:smart]&.start_with?("areca,") }
538
539   disks.each do |disk|
540     munin_plugin "smart_#{disk[:munin]}" do
541       target "smart_"
542       conf "munin.smart.erb"
543       conf_variables :disk => disk
544     end
545   end
546 else
547   service "smartd" do
548     action [:stop, :disable]
549   end
550 end
551
552 if disks.count.positive?
553   munin_plugin "hddtemp_smartctl" do
554     conf "munin.hddtemp.erb"
555     conf_variables :disks => disks
556   end
557 else
558   munin_plugin "hddtemp_smartctl" do
559     action :delete
560     conf "munin.hddtemp.erb"
561   end
562 end
563
564 plugins = Dir.glob("/etc/munin/plugins/smart_*").map { |p| File.basename(p) } -
565           disks.map { |d| "smart_#{d[:munin]}" }
566
567 plugins.each do |plugin|
568   munin_plugin plugin do
569     action :delete
570     conf "munin.smart.erb"
571   end
572 end
573
574 if File.exist?("/etc/mdadm/mdadm.conf")
575   mdadm_conf = edit_file "/etc/mdadm/mdadm.conf" do |line|
576     line.gsub!(/^MAILADDR .*$/, "MAILADDR admins@openstreetmap.org")
577
578     line
579   end
580
581   file "/etc/mdadm/mdadm.conf" do
582     owner "root"
583     group "root"
584     mode "644"
585     content mdadm_conf
586   end
587
588   service "mdmonitor" do
589     action :nothing
590     subscribes :restart, "file[/etc/mdadm/mdadm.conf]"
591   end
592 end
593
594 file "/etc/modules" do
595   action :delete
596 end
597
598 node[:hardware][:modules].each do |module_name|
599   kernel_module module_name do
600     action :install
601     not_if { kitchen? }
602   end
603 end
604
605 node[:hardware][:blacklisted_modules].each do |module_name|
606   kernel_module module_name do
607     action :blacklist
608   end
609 end
610
611 if node[:hardware][:watchdog]
612   package "watchdog"
613
614   template "/etc/default/watchdog" do
615     source "watchdog.erb"
616     owner "root"
617     group "root"
618     mode "644"
619     variables :module => node[:hardware][:watchdog]
620   end
621
622   service "watchdog" do
623     action [:enable, :start]
624   end
625 end
626
627 unless Dir.glob("/sys/class/hwmon/hwmon*").empty?
628   package "lm-sensors"
629
630   Dir.glob("/sys/devices/platform/coretemp.*").each do |coretemp|
631     cpu = File.basename(coretemp).sub("coretemp.", "").to_i
632     chip = format("coretemp-isa-%04d", cpu)
633
634     temps = if File.exist?("#{coretemp}/name")
635               Dir.glob("#{coretemp}/temp*_input").map do |temp|
636                 File.basename(temp).sub("temp", "").sub("_input", "").to_i
637               end.sort
638             else
639               Dir.glob("#{coretemp}/hwmon/hwmon*/temp*_input").map do |temp|
640                 File.basename(temp).sub("temp", "").sub("_input", "").to_i
641               end.sort
642             end
643
644     if temps.first == 1
645       node.default[:hardware][:sensors][chip][:temps][:temp1][:label] = "CPU #{cpu}"
646       temps.shift
647     end
648
649     temps.each_with_index do |temp, index|
650       node.default[:hardware][:sensors][chip][:temps]["temp#{temp}"][:label] = "CPU #{cpu} Core #{index}"
651     end
652   end
653
654   execute "/etc/sensors.d/chef.conf" do
655     action :nothing
656     command "/usr/bin/sensors -s"
657     user "root"
658     group "root"
659   end
660
661   template "/etc/sensors.d/chef.conf" do
662     source "sensors.conf.erb"
663     owner "root"
664     group "root"
665     mode "644"
666     notifies :run, "execute[/etc/sensors.d/chef.conf]"
667   end
668 end
669
670 if node[:hardware][:shm_size]
671   execute "remount-dev-shm" do
672     action :nothing
673     command "/bin/mount -o remount /dev/shm"
674     user "root"
675     group "root"
676   end
677
678   mount "/dev/shm" do
679     action :enable
680     device "tmpfs"
681     fstype "tmpfs"
682     options "rw,nosuid,nodev,size=#{node[:hardware][:shm_size]}"
683     notifies :run, "execute[remount-dev-shm]"
684   end
685 end
686
687 prometheus_collector "ohai" do
688   interval "15m"
689 end