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